4 ms·
Do you have any examples of the “perfectly safe code the Rust compiler will nag you about”? Not trying to start language wars, just genuinely curious as someone
by jonpalmisc 4y ago
Do you have any examples of the “perfectly safe code the Rust compiler will nag you about”? Not trying to start language wars, just genuinely curious as someone who writes Rust on occasion.
- tayo42 4y agoI dont have an example in front of me. I had write some weird code to prevent a variable from being dropped unexpectedly.
- ben0x539 4y agomy go-to example is the non-lexical lifetimes stuff, like when you're somewhere in a match and you don't get to return a reference to a thing because there's another reference that's clearly not relevant anymore at the top of the match
- kibwen 4y agoThough it's worth mentioning that Rust gained support for many non-lexical lifetime patterns a few years ago, and has designs on supporting more in the future.
- melissalobos 4y ago> Do you have any examples of the “perfectly safe code the Rust compiler will nag you about”? Not an example, but if the borrow checker approved every proper program in a reasonable time, then it could solve the halting problem.
- oxff 4y agoI don't think the compiler can tell if you are mutably indexing to two different items in a collection like Vec.
- alkonaut 4y agoIt's not very good at knowing when something doesn't alias, for example. E.g. if you do this, the compiler doesn't realize it's safe to do because the array locations are different so I'm not borrowing the specific location mutably more than once. Instead it nags me that I have to split the array into non-overlapping slices. if (i != j) swap_items(&mut arr[i], &mut arr[j]); A contrived example and obviously the same can be achieved in many other ways, most of which the compiler would be happier about - but that's often the case with Rust: a seemingly safe thing isn't quite safe enough for the compiler so you have to do it differently. And that's the main problem of ergonomics in the borrow checker imo. This is helped enormously by helpful error messages, and there is great progress on fixing little paper cuts and improving the borrow checker to make more valid programs accepted by the borrow checker. But it doesn't contain a massive AI or theorem prover so there will always be situations where you'll need unsafe despite not actually being unsafe, or when you'll do something a bit more contrived than you might have expected.
- oconnor663 4y agoThis is a good example. Another common situation is a struct that has both `foo` and `bar` members and then exposes both `.get_foo_mut()` and `.get_bar_mut()`. (Again this is a trivial example, and maybe in the real world they do more work before returning those references.) The problem is that it's illegal to call either of those while the return value from the other is still alive. Even though we know they don't alias each other, and we could totally accomplish the same thing if the members were public, there's no way for the method signatures to express what parts of the struct they don't touch.
- alkonaut 4y agoI think there is work going on in this area (borrowing parts of structs)? Might have dreamt.
- Measter 4y agoI ran into a borrow check error on code like this[0]: enum Inner { A(i32), B(i32) } enum Outer { Foo{ field: Inner } } fn do_foo(val: &mut Outer) { match val { Outer::Foo{field: f @ Inner::A(id)} if *id == 3 => { *f = Inner::B(25); }, _ => {} } } The compiler is seeing the `id` and `f` references as overlapping for the entire arm, even though the use of `id` and `f` are not interleaved. Bearing in mind that I don't actually know how the compiler works here, but I don't think this is a borrow checker limitation in and of itself, rather what I think is happening is that in the match expression the compiler is creating both `id` and `f` directly from `val`, creating the overlapping borrow. The reason I believe that is that this equivalent code results in the same error[1]: fn do_foo(val: &mut Outer) { let f = val.get_inner(); let id = val.get_inner().get_a(); if *id == 3 { *f = Inner::B(25); } } Whereas if you create the `id` reference from `f` instead of from `val` the compiler accepts it because `f` is not used between `id`s creation and death[2]: fn do_foo(val: &mut Outer) { let f = val.get_inner(); let id = f.get_a(); if *id == 3 { *f = Inner::B(25); } } [0] Playground link: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=28a84770cb1f0eaadb9695284d974e41 https://play.rust-lang.org/?version=stable&mode=debug&editio... [1] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=55dd6b79a94eaccc81a126281a96015c https://play.rust-lang.org/?version=stable&mode=debug&editio... [2] https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=3115af527960a3cf9f763e38fa55e70e https://play.rust-lang.org/?version=stable&mode=debug&editio...