3

Background

I know that in Rust people prefer &str rather than &String. But in some case we were only given &String.

One example is when you call std::iter::Iterator::peekable. The return value is a Peekable<I> object that wraps the original iterator into it and gives you one extra method peek.

The point here is that peek only gives you a reference to the iterator item. So if you have an iterator that contains Strings, you only have &String in this case. Of cause, you can easily use as_str to get a &str but in the code I will show below it is equivalent to a call to clone.

The question

This code

#[derive(Debug)]
struct MyStruct(String);

impl MyStruct {
    fn new<T>(t: T) -> MyStruct
    where
        T: Into<String>,
    {
        MyStruct(t.into())
    }
}

fn main() {
    let s: String = "Hello world!".into();
    let st: MyStruct = MyStruct::new(&s);
    println!("{:?}", st);
}

doesn't compile because String doesn't implement From<&String>. This is not intuitive.

Why does this not work? Is it just a missing feature of the standard library or there are some other reasons that prevent the standard library from implementing it?

In the real code, I only have a reference to a String and I know to make it work I only need to call clone instead, but I want to know why.

Earth Engine
  • 10,048
  • 5
  • 48
  • 78
  • 2
    I wouldn't say it is anti-intuitive - other objects don't do it either, e.g. `Vec` doesn't `impl From<&Vec>`; also `impl<'a> From<&'a str>` is much a more probable use case and it's readily available. – ljedrz Jul 15 '17 at 12:22
  • I feel that the real answer have something to do with my relevent question https://stackoverflow.com/questions/45126120/if-intostring-not-implemented-for-string-why-these-implementations-are-co – Earth Engine Jul 16 '17 at 08:05

2 Answers2

4

To solve your problem, one could imagine adding a new generic impl to the standard library:

impl<'a, T: Clone> From<&'a T> for T { ... }

Or to make it more generic:

impl<B, O> From<B> for O where B: ToOwned<Owned=O> { ... }

However, there are two problems with doing that:

  1. Specialization: the specialization feature that allows to overlapping trait-impls is still unstable. It turns out that designing specialization in a sound way is way more difficult than expected (mostly due to lifetimes).

    Without it being stable, the Rust devs are very careful not to expose that feature somewhere in the standard library's public API. This doesn't mean that it isn't used at all in std! A famous example is the specialized ToString impl for str. It was introduced in this PR. As you can read in the PR's discussion, they only accepted it because it does not change the API (to_string() was already implemented for str).

    However, it's different when we would add the generic impl above: it would change the API. Thus, it's not allowed in std yet.

  2. core vs std: the traits From and Into are defined in the core library, whereas Clone and ToOwned are defined in std. This means that we can't add a generic impl in core, because core doesn't know anything about std. But we also can't add the generic impl in std, because generic impls need to be in the same crate as the trait (it's a consequence of the orphan rules).

    Thus, it would required some form of refactoring and moving around definitions (which may or may not be difficult) before able to add such a generic impl.


Note that adding

impl<'a> From<&'a String> for String { ... }

... works just fine. It doesn't require specialization and doesn't have problems with orphan rules. But of course, we wouldn't want to add a specific impl, when the generic impl would make sense.

(thanks to the lovely people on IRC for explaining stuff to me)

Lukas Kalbertodt
  • 79,749
  • 26
  • 255
  • 305
1

Since String does implement From<&str>, you can make a simple change:

fn main() {
    let s: String = "Hello world!".into();
    // Replace &s with s.as_str()
    let st: MyStruct = MyStruct::new(s.as_str());
    println!("{:?}", st);
}

All &Strings can be trivially converted into &str via as_str, which is why all APIs should prefer to use &str; it's a strict superset of accepting &String.

Lucretiel
  • 3,145
  • 1
  • 24
  • 52