Изменчивость элемента по сути является частью имени переменной в ржавчине. Возьмем, к примеру, этот код:
let mut foo = String::new();
let foo = foo;
let mut foo = foo;
foo
внезапно становится неизменным, но это не означает, что первые два foo
s не существуют.
С другой стороны, изменяемая ссылка прикреплена к времени жизни объекта и поэтому привязана к типу и будет существовать в течение своего собственного времени жизни, запрещая любой вид доступа к исходному объекту, если он не через ссылку.
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);
Это не компилируется. Если вымогли бы аннотировать время жизни, они выглядели бы примерно так:
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 (Non Lexical Lifetimes), будет скомпилирован следующий код:
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
is!Send + !Sync
, он может жить только в том потоке, в котором он был создан. Примером является Rc
, который не может отправить свою копию между потоками, потому что он не может подсчитывать ссылки атомарно (посмотрите на Arc
, чтобы сделать это), и так как он не несет времени жизничтобы заставить одну копию себя существовать при отправке через границу потока, поэтому она не может быть отправлена через потоки.
- Я использую
&str
вместо &String
в моем третьем примере, это потому, что String: Deref<str>
(вам может понадобиться прокрутить вниз, чтобы увидеть его)и, следовательно, везде, где мне нужно &str
, я могу добавить &String
, потому что компилятор автоматически определит его.