下面的示例显示了尝试连接两个字符串的两个函数foo()
和bar()
。如果左侧+
参数为&String
,则代码将无法编译。
这在foo()
中举例说明,无法使用错误消息 [E0369] 进行编译。函数bar()
表明我可以通过克隆LHS参数来摆脱这种情况。我的直觉表明这种克隆应该是多余的。
为什么我必须clone()
LHS论证?这是Rust +
- 运算符的当前实现状态的反映,还是有一个有意深思的背后?
// rustc 1.7.0-nightly (110df043b 2015-12-13)
fn foo(a: &String, i: i32) -> String {
a + &i.to_string() // .. '+' cannot be applied to type & .. String [E0369]
}
fn bar(a: &String, i: i32) -> String {
a.clone() + &i.to_string() // Ok
}
#[test]
fn my_test() {
assert!(foo(&"s".to_string(), 42) == "s42"); // fn foo(..) failed to compile
assert!(bar(&"s".to_string(), 42) == "s42"); // Ok
}
答案 0 :(得分:5)
如果您查看Add
特征(或当前nightly docs for Add
)的文档,您会看到:
impl<'a> Add<&'a str> for String
这是Add
的{{1}}的唯一实现。此签名表示左侧按值而不是引用,因此需要调用String
。
要澄清一下,clone()
Add<&str>
只是String
的封套,它附加到String::push_str
的末尾,重新使用现有的分配。通过不可变指针执行此操作不可能,并且通过可变指针执行此操作将真的奇怪并且与合理的期望相反。因此,它要求LHS按值传递。
根据您的要求,为{em>任何形式的String
定义的+
运算符是疣,因此是否定义了其他表单是...好吧,就我所知,一个悬而未决的问题。
答案 1 :(得分:3)
&String
是一件非常奇怪的事情。你几乎总是应该选择&str
。
引起我注意的是你说的
这意味着字符串
a
的外部克隆是必要的,并且意味着不会浪费周期。
好吧,你在浪费周期 - &i.to_string()
分配然后丢弃它的缓冲区。
to_string
是特质ToString
的一部分。它默认为Display
类型实现为
impl<T: fmt::Display + ?Sized> ToString for T {
#[inline]
fn to_string(&self) -> String {
use core::fmt::Write;
let mut buf = String::new();
let _ = buf.write_fmt(format_args!("{}", self));
buf.shrink_to_fit();
buf
}
}
请注意,这是直接写入String
。你可以对foo
喜欢
use std::fmt::Write;
fn foo(a: &str, i: i32) -> String {
let mut buf: String = a.into();
write!(&mut buf, "{}", i).unwrap();
buf
}
更好的方法是智能地将buf
预先分配到足够的大小。这是最差a.len() + 11
,因为i
将为字符串贡献最多11个字符。如果您知道i
处于更有限的范围内,您可能会做得更好。
use std::fmt::Write;
fn foo(a: &str, i: i32) -> String {
let mut buf = String::with_capacity(a.len() + 11);
write!(&mut buf, "{}{}", a, i).unwrap();
buf
}
你可能在这一点上想知道为什么我这样做。这是一个合理的问题。
我不知道。