3 ms·
Looks really nicely written and laid out. One note on borrowing/ownership that really made it click for me was that to move from &mut T to &T you have to trans
by vvanders 9y ago
Looks really nicely written and laid out.
One note on borrowing/ownership that really made it click for me was that to move from &mut T to &T you have to transition through owning the value type, so:
&mut T -> T <- &T
Whichever scope holds the actual value-type is the only scope that can make that transition(absent cases like RefMut)
- red75prime 9y agoDowngrading &mut T to &T is perfectly valid in every context[0]. Upgrading from &T to &mut T is impossible in safe Rust and can easily cause undefined behavior when it's done in unsafe block. If you have T, you can create one &mut T reference or many &T references. [0] Except in a case when there's &mut U reference, which points at some part of the object referenced by &mut T.
- danieldk 9y agoCould you clarify you mean here? Something like this is perfectly valid: fn no_mut(a: &mut usize) { *a = 10; let b: &usize = a; println!("a: {}, b: {}", a, b); } fn main() { let mut foo = 42usize; no_mut(&mut foo); } If such pointer weakening was not possible, how would you be able to call a method that takes &self on binding of type &mut self? Of course, you can't use a mutably while b is in scope. So, something like the following results in a compiler error: fn no_mut(a: &mut usize) { let b: &usize = a; *a = 10; println!("a: {}, b: {}", a, b); }
- vvanders 9y agoActually you're right, I didn't realize that the compiler treated a single &mut references like that. I wonder if what I'm thinking of involved &mut self captures that were happening as part of an impl on a struct. There's definitely some cases there where I was getting tripped up on it.
- vvanders 9y agoAh, now I remember why I tend to use this model: use std::thread; fn no_mut(a: &mut usize) { *a = 10; { let b: &usize = a; println!("a: {}, b: {}", a, b); } let b: &usize = a; thread::spawn(|| { println!("b: {}", b); }); } fn main() { let mut foo = 42usize; no_mut(&mut foo); } Similar code but the move throws an error(as it should). If you don't have the value type then you're constrained by the parent mut lifetime here so it's not just as simple as a conversion that you can then pass around. Otherwise you need to consider all the lifetime scopes which can get a bit unwieldy.
- deleted 9y ago[deleted]
- danieldk 9y agoIf you don't have the value type then you're constrained by the parent mut lifetime here so it's not just as simple as a conversion that you can then pass around. You cannot move any closure to spawn that captures a non-static reference, since there is no guarantee that the value will live during the lifetime of the thread. (The closure is required to be Send + 'static.) E.g., you can't do (moving to a non-Copy type :)): let foo = String::new(); let t = thread::spawn(|| { println!("foo: {}", &foo); }); You could move the captures, including foo, but then foo cannot be used in its original scope anymore: let foo = String::new(); let t = thread::spawn(move || { println!("foo: {}", &foo); }); // This will give an error, because foo is moved. println!("{}", foo); Note that only moving values works, this does not compile (for the same reasons as the first example): let foo = String::new(); let bar = &foo; let t = thread::spawn(move || { println!("bar: {}", &bar); });
- deleted 9y ago[deleted]