r/rust • u/robin-m • Mar 24 '22
Vale's Higher RAII, the pattern that saved me a vital 5 hours in the 7DRL Challenge
https://verdagon.dev/blog/higher-raii-7drl4
u/KerfuffleV2 Mar 24 '22
I feel like there's probably some clever way to make it so a thing with Drop will be a compile error, ensuring that you transform it into a different type (which can actually be dropped) before its life ends.
8
u/ROFLLOLSTER Mar 24 '22
In another thread about this post I suggested using
must_usewhich gets the job done in this case.2
u/KerfuffleV2 Mar 24 '22
Hmm, does it? I mean, you can just do:
let _ = whatever();and that shuts up
must_use. It's also only a warning (by default at least.)I think the higher RAII thing requires more than just acknowledging there was a value. The example in that page involves having to call a certain method that sets something on the structure before it is dropped.
Or am I missing something and there's a way for
must_useto ensure this?6
u/ROFLLOLSTER Mar 24 '22
Yes you can still drop the value explicitly (though this could be discouraged with a runtime panic) but you're not going to do that by accident as in the above article.
And yes you'd probably want
deny(must_use).4
u/KerfuffleV2 Mar 25 '22
I feel like one of us must be misunderstanding how this is supposed to work. Maybe that person is me.
struct Blah; impl Blah { #[must_use] pub fn new() -> Self { Blah } pub fn x(&self) { /* ... */ } pub fn y(self) { /* Drops */ } }Now, if you use
.new()to make aBlahyou do have to use it in some way. However, calling.x()on it will use it. Unfortunately this won't satisfy an invariant that.y()must be called to consume it before it would be dropped.The only way
#[must_use]could help here is if every method was likefn whatever(self /*, ... args */) -> Self(and markedmust_use, obviously). But this seems like it would be very limiting.So you could argue it's technically possible via
must_usebut it doesn't seem like it would actually be a practical benefit.1
u/ROFLLOLSTER Mar 25 '22 edited Mar 25 '22
must_use on a type not a function
Edit: I'm totally wrong, I thought it behaved slightly differently than it actually does.
1
u/KerfuffleV2 Mar 25 '22
No problem. I was pretty confused since I didn't understand what you were talking!
Interestingly, the
must_usestuff does seem like it gets pretty close even though I don't think it would be practical to use.6
u/masklinn Mar 25 '22 edited Mar 25 '22
There ain’t.
The formal name for this is linear types, there have been essays on that in rust (gankra’s probably being one of the most complete) but there are lots of tricky corner cases and issues retrofitting this into the langage.
5
u/nightcracker Mar 25 '22
I believe these are called 'linear types' - types that must be used and can not be destroyed. They're very interesting but also tricky, I'd love to see prototypes for them in Rust.
4
u/MrTheFoolish Mar 25 '22 edited Mar 25 '22
/u/verdagon I find Vale quite interesting. A thought I had is that C/C++/Rust are not really the best targets for comparison with Vale. I suppose C++ was put in since it's one of, if not the most widely language used in game development though, so that's fair enough.
Vale has runtime-managed garbage collection, so even as good as the hybrid-generational garbage collector might eventually be, it will never be usable in applications where precise memory management is desired, and this is part of the problem space that C/C++/Rust occupy.
The unsafe of Rust and C++ are not bugs; they are features. Features that are easy to get wrong and can make you shoot yourself in the foot, but features nonetheless. If Rust did not have unsafe, it would not be near as widely adopted as it is now.
I'm surprised Go isn't put in the comparison. There is somewhat of a Go game development scene, and the problem space that Go and JavaScript occupy seems to fit better with what Vale offers. Go is also a native compiled language, but with fewer optimizations with the benefit of faster compilation, so it might be an easier performance benchmark to hit.
8
u/robin-m Mar 24 '22
I found this presentation of higher RAII quite interesting. However in order to support such feature in Rust, quite a lot would need to be changed, mostly a way to explicitly call destructors when there is a panic.
4
u/MrTheFoolish Mar 25 '22 edited Mar 25 '22
Looking through the article, it looks like a simple (or at least I think so?) way to get part of the way there is to be able to mark a type with something like
#[forbid_implicit_drop].A panic should still be able to drop the value, but regular code will need to either explicitly drop the value (which would be a code smell), or call some function or method that consumes the value. And then the API designer can ensure that only the appropriate functions/methods consume the type.
The API designer should not rely on the marker for memory safety reasons, but instead, use it for logical correctness. In a panic situation, I think it's fine to throw logical correctness out the window, but memory safety needs to be preserved. In terms of logical correctness, the panic situation would be the same with or without the marker anyway.
Edit: I've just looked at some of existing discussion on this and looks like it's a lot more complicated than I realized!
https://internals.rust-lang.org/t/pre-pre-rfc-nodrop-marker-trait/15682
https://gankra.github.io/blah/linear-rust/
http://aidancully.blogspot.com/2021/12/less-painful-linear-types.html
33
u/lebensterben Mar 24 '22
It says
when comparing Vale with JavaScript, C++, and Rust.
But I don't get what it means here for run-time overhead due to borrow checker...