为什么 String 不实现 From<&String>?
Why doesn't String implement From<&String>?
背景
我知道在 Rust 中人们更喜欢 &str
而不是 &String
。但在某些情况下,我们只得到 &String
.
一个例子是你调用std::iter::Iterator::peekable
。 return 值是一个 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
,但我想知道为什么。
为了解决您的问题,可以想象向标准库添加一个新的通用实现:
impl<'a, T: Clone> From<&'a T> for T { ... }
或者让它更通用:
impl<B, O> From<B> for O where B: ToOwned<Owned=O> { ... }
但是,这样做有两个问题:
Specialization:允许重叠 trait-impls 的专业化特性仍然不稳定。事实证明,以合理的方式设计专业化是方式more difficult than expected(主要是由于生命周期)。
如果它不是稳定的,Rust 开发者会非常小心不要在标准库的 public API 的某处公开该功能。这并不意味着它根本没有在 std 中使用!一个著名的例子是 str
的专用 ToString
impl。它被引入 in this PR。正如您在 PR 的讨论中看到的那样,他们只接受了它,因为它没有改变 API(to_string()
已经为 str
实现)。
但是,当我们添加上面的通用 impl 时情况不同:它会更改 API。因此,在 std 中还不允许。
core
vs std
:特征 From
和 Into
定义在
core
library,而 Clone
和 ToOwned
在 std
中定义。这意味着我们不能在 core
中添加泛型实现,因为 core
对 std
一无所知。但是我们也不能在 std
中添加泛型 impl,因为泛型 impl 需要与 trait 在同一个 crate 中(这是孤儿规则的结果)。
因此,在能够添加这样一个通用的实现之前,它需要某种形式的重构和移动定义(这可能困难也可能不困难)。
注意添加
impl<'a> From<&'a String> for String { ... }
... 工作得很好。它不需要专业化,也没有孤儿规则的问题。但当然,当通用实现有意义时,我们不想添加特定实现。
(感谢 IRC 上可爱的人们向我解释东西)
由于 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
都可以通过 as_str
简单地转换为 &str
,这就是为什么所有 API 都应该更喜欢使用 &str
的原因;它是接受 &String
.
的严格超集
背景
我知道在 Rust 中人们更喜欢 &str
而不是 &String
。但在某些情况下,我们只得到 &String
.
一个例子是你调用std::iter::Iterator::peekable
。 return 值是一个 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
,但我想知道为什么。
为了解决您的问题,可以想象向标准库添加一个新的通用实现:
impl<'a, T: Clone> From<&'a T> for T { ... }
或者让它更通用:
impl<B, O> From<B> for O where B: ToOwned<Owned=O> { ... }
但是,这样做有两个问题:
Specialization:允许重叠 trait-impls 的专业化特性仍然不稳定。事实证明,以合理的方式设计专业化是方式more difficult than expected(主要是由于生命周期)。
如果它不是稳定的,Rust 开发者会非常小心不要在标准库的 public API 的某处公开该功能。这并不意味着它根本没有在 std 中使用!一个著名的例子是
str
的专用ToString
impl。它被引入 in this PR。正如您在 PR 的讨论中看到的那样,他们只接受了它,因为它没有改变 API(to_string()
已经为str
实现)。但是,当我们添加上面的通用 impl 时情况不同:它会更改 API。因此,在 std 中还不允许。
core
vsstd
:特征From
和Into
定义在core
library,而Clone
和ToOwned
在std
中定义。这意味着我们不能在core
中添加泛型实现,因为core
对std
一无所知。但是我们也不能在std
中添加泛型 impl,因为泛型 impl 需要与 trait 在同一个 crate 中(这是孤儿规则的结果)。因此,在能够添加这样一个通用的实现之前,它需要某种形式的重构和移动定义(这可能困难也可能不困难)。
注意添加
impl<'a> From<&'a String> for String { ... }
... 工作得很好。它不需要专业化,也没有孤儿规则的问题。但当然,当通用实现有意义时,我们不想添加特定实现。
(感谢 IRC 上可爱的人们向我解释东西)
由于 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
都可以通过 as_str
简单地转换为 &str
,这就是为什么所有 API 都应该更喜欢使用 &str
的原因;它是接受 &String
.