一个项目的可变性在 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;
let baz = &foo;
immutably_use_value(bar);
foo.push_str("Hello world!");
immutably_use_value(baz);
这段代码无法编译通过。 如果你能给生命周期加上注释,它们看起来会类似于这样:
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!
一些额外的注意事项:
Due to NLL (Non Lexical Lifetimes), the following code will compile:
let mut foo = String::new();
let bar = &foo;
foo.push_str("Abc");
Because bar
is not used after the mutable use of foo
.
You mention threading, which has its own constraints and traits involved:
The Send
trait will allow you to give ownership of a variable across a thread.
The Sync
trait will allow you to share a reference to a variable across a thread. This includes mutable references, as long as the original thread does not use the object for the duration of the borrow.
A few examples:
- Type
T
is Send + Sync
, it can be sent across threads and be shared between them
- Type
T
is !Send + Sync
, it can be shared across threads, but not sent between them. An example is a window handle that can only be destroyed on the original thread.
- Type
T
is Send + !Sync
, it can be sent across threads, but not shared between them. An example is RefCell
, which will can only use its runtime borrow-checking on a single thread due to it not using atomics (Multithreading safe components).
- Type
T
is !Send + !Sync
, it can only live on the thread it was created on. An example is Rc
, which cannot send a copy of itself across threads because it cannot count references atomically (Look at Arc
to do that) and since it carries no lifetimes to force a single copy of itself to exist when sending across a thread boundary, it therefore cannot be sent across threads.
- I use
&str
instead of &String
in my third example, this is because String: Deref<str>
(You may need to scroll down to see it), and therefore anywhere I need a &str
I can chuck a &String
in because the compiler will autoderef.