我正在研究Rust Book(第4章),而令我感到惊讶的是这样的代码compiles:
fn main() {
let mut s = String::from("hello");
let r1 = &s;
let r2 = &s;
println!("{}, {}", r1, r2);
// this line silences the warning: 'variable does not need to be mutable'
s.push_str(" world");
}
为什么Rust允许对可变变量的不变引用?这似乎会削弱安全保证。如果我有一个可变的变量,并且将不可变的引用传递给某些线程,则这些线程假设值不会更改,但是我可以通过原始变量来对该值进行更改。
我还没有到达线程,但是发现这个奇怪,在这种情况下,与C ++没什么不同
void doNotChangeMyString(const std::string& myConstString) {
// ... assume that myConstString cannot change and use it on a thread
// return immediately even though some worker thread is still
// using myConstString
}
void main() {
std::string s = "hello" // not const!
doNotChangeMyString(s);
s = "world"; // oops
}
编辑:我修复了Rust代码,以便对其进行编译。请重新考虑否决票并关闭投票。接受的答案说明了一个我从Rust Book的借书一章中没有得到的概念,它对我非常有帮助,并且可以帮助其他同时学习Rust的人。
答案 0 :(得分:4)
项目的可变性实质上是rust变量名称的一部分。以下面的代码为例:
let mut foo = String::new();
let foo = foo;
let mut foo = foo;
foo
突然变得不可变,但这并不意味着前两个foo
不存在。
另一方面,可变引用附加到对象的生存期,因此是类型绑定的,并且将存在其自身的生存期,不允许对原始对象进行任何类型的访问如果不是通过参考。
let mut my_string = String::new();
my_string.push_str("This is ok! ");
let foo: &mut String = &mut my_string;
foo.push_str("This goes through the mutable reference, and is therefore ok! ");
my_string.push_str("This is not ok, and will not compile because `foo` still exists");
println!("We use foo here because of non lexical lifetimes: {:?}", foo);
对my_string.push_str
的第二次调用将不会编译,因为之后可以使用foo
(在这种情况下,可以保证使用)。
您的特定问题要求类似以下内容,但您甚至不需要多线程即可对此进行测试:
fn immutably_use_value(x: &str) {
println!("{:?}", x);
}
let mut foo = String::new();
let bar = &foo; //This now has immutable access to the mutable object.
let baz = &foo; //Two points are allowed to observe a value at the same time. (Ignoring `Sync`)
immutably_use_value(bar); //Ok, we can observe it immutably
foo.push_str("Hello world!"); //This would be ok... but we use the immutable references later!
immutably_use_value(baz);
This does not compile.如果您可以注释生命周期,它们看起来将类似于以下内容:
let mut foo = String::new(); //Has lifetime 'foo
let bar: &'foo String = &foo; //Has lifetime 'bar: 'foo
let baz: &'foo String = &foo; //Has lifetime 'baz: 'foo
//On the other hand:
let mut foo = String::new(); //Has lifetime 'foo
let bar: &'foo mut String = &mut foo; //Has lifetime 'bar: mut 'foo
let baz: &'foo mut String = &mut foo; //Error, we cannot have overlapping mutable borrows for the same object!
一些注意事项:
由于NLL(非词法生存时间),将编译以下代码:
let mut foo = String::new();
let bar = &foo;
foo.push_str("Abc");
因为bar
的可变使用后没有使用foo
。
您提到了线程,它具有其自身的约束和特征:
Send
特性将允许您在线程中授予变量的所有权。
Sync
特征将使您可以跨线程共享对变量的引用。只要原始线程在借用期间不使用该对象,这将包括可变引用。
一些例子:
T
是Send + Sync
,它可以跨线程发送并在线程之间共享T
为!Send + Sync
,它可以在线程之间共享,但不能在线程之间发送。一个示例是只能在原始线程上销毁的窗口句柄。T
是Send + !Sync
,它可以跨线程发送,但不能在线程之间共享。 RefCell
是一个示例,由于它不使用原子(多线程安全组件),因此只能在单个线程上使用运行时借阅检查。T
为!Send + !Sync
,它只能存在于创建它的线程上。 Rc
是一个示例,它无法跨线程发送其自身的副本,因为它无法原子计数引用(请看Arc
来做到这一点),并且因为它没有生命周期来强制将其自身的单个副本传递给跨线程边界发送时存在,因此无法跨线程发送。 &str
而不是&String
,这是因为String: Deref<str>
(您可能需要向下滚动才能看到它),因此在任何地方我都需要{{ 1}}我可以插入&str
,因为编译器会自动反引用。