背景
我知道在Rust中,人们更喜欢&str
而不是&String
。但在某些情况下,我们只给了&String
。
一个例子是当你致电std::iter::Iterator::peekable
时。返回值是一个Peekable<I>
对象,它将原始迭代器包装到其中,并为您提供一个额外的方法peek
。
这里的要点是peek
仅提供对迭代器项的引用。因此,如果你有一个包含String
的迭代器,那么在这种情况下你只有&String
。当然,您可以轻松使用as_str
获取&str
,但在下面显示的代码中,它相当于对clone
的调用。
问题
此代码
#[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);
}
没有编译,因为String
没有实现From<&String>
。这不直观。
为什么这不起作用?它只是标准库的缺失功能还是有其他一些阻止标准库实现的原因?
在真实代码中,我只提及String
,我知道要使其正常工作我只需要调用clone
,但我想知道原因。
答案 0 :(得分:4)
要解决您的问题,可以想象在标准库中添加新的通用impl:
impl<'a, T: Clone> From<&'a T> for T { ... }
或者使其更通用:
impl<B, O> From<B> for O where B: ToOwned<Owned=O> { ... }
但是,这样做有两个问题:
专业化:允许重叠trait-impls的特化功能仍然不稳定。事实证明,以合理的方式设计专业化的方式是more difficult than expected(主要是由于生命周期)。
如果不稳定,Rust开发人员非常小心,不要在标准库的公共API中的某处公开该功能。这并不意味着std中根本没有使用它!一个着名的例子是ToString
的专门str
impl。它被引入in this PR。正如您在PR的讨论中所读到的那样,他们只接受了它,因为它不会更改API(to_string()
已经实现str
)。
然而,当我们添加上面的通用impl时会有所不同:它会改变API。因此,它尚未被允许进入标准状态。
core
vs std
:特征From
和Into
在
core
library,而Clone
和ToOwned
在std
中定义。这意味着我们无法在core
中添加通用impl,因为core
对std
一无所知。但我们也无法在std
中添加泛型impl,因为泛型impls需要与特征在同一个crate中(这是孤儿规则的结果)。
因此,在能够添加这样的通用impl之前,它需要某种形式的重构和移动定义(可能或可能不困难)。
请注意添加
impl<'a> From<&'a String> for String { ... }
......工作正常。它不需要专业化,也没有孤儿规则的问题。但是,当通用impl有意义时,我们当然不希望添加特定的impl。
(感谢IRC上可爱的人向我解释的东西)
答案 1 :(得分:1)
由于String
确实实现了From<&str>
,因此您可以进行简单的更改:
fn main() {
let s: String = "Hello world!".into();
// Replace &s with s.as_str()
let st: MyStruct = MyStruct::new(s.as_str());
println!("{:?}", st);
}
所有&String
都可以通过&str
轻松转换为as_str
,这就是为什么所有API都应该首选使用&str
的原因;这是接受&String
的严格超集。