问题
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 String
s, 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.
回答1:
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:
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 forstr
. 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 forstr
).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.
core
vsstd
: the traitsFrom
andInto
are defined in the core library, whereasClone
andToOwned
are defined instd
. This means that we can't add a generic impl incore
, becausecore
doesn't know anything aboutstd
. But we also can't add the generic impl instd
, 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)
回答2:
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 &String
s 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
.
来源:https://stackoverflow.com/questions/45118060/why-doesnt-string-implement-fromstring