Rust 1.61.0 released
Rust 1.61.0 released
Posted May 21, 2022 9:07 UTC (Sat) by excors (subscriber, #95769)Parent article: Rust 1.61.0 released
(In my currently very limited experience, I do find it slightly irritating how frequently I run into Rust limitations and then find a GitHub issue where other people have had the same problem and there's a proposed solution but it hasn't been stabilised yet (and might not be for months or years) so I can't rely on it. But in most cases there is a viable workaround, it's just not as obvious or elegant as I'd like. And most problems are because I'm straying from idiomatic Rust by avoiding all heap allocation (so e.g. no structs containing closures or trait objects), so I'm relying more heavily on generics and wishing they were as powerful as C++ templates but they're really not, and it just about works for that unidiomatic use case anyway.)
Posted May 24, 2022 10:49 UTC (Tue)
by ballombe (subscriber, #9523)
[Link] (11 responses)
Posted May 24, 2022 11:14 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link]
Posted May 24, 2022 12:49 UTC (Tue)
by excors (subscriber, #95769)
[Link] (6 responses)
(Rust does seem to have a tendency to use a lot of stack space for temporaries, because it's common to move objects by value and rely on the compiler to elide the memcpy()s. E.g. "let array = [42; 1000];" is constructing an array with 1000 copies of 42 then moving it to the local variable, and the compiler could (and often does) construct it in-place, but sometimes the compiler allocates twice as much stack space and does an explicit memcpy from the temporary to the local. If you have small stacks, you need to be wary of that.)
If you mean dynamic memory allocation on a heap, then Rust has no_std - you get a minimal version of the standard library with no heap, no threading, no IO, etc, which works on bare-metal targets. That seems generally quite usable, and there are plenty of third-party crates that are compatible with no_std (e.g. https://docs.rs/heapless/latest/heapless/ which replicates some std collection types with only static allocation).
no_std does bring some Rust-specific annoyances though, in my (limited) experience. E.g. Rust is strict about verifying lifetimes, and if you have complicated dynamic lifetimes then normally you could replace standard references with std::rc::Rc (reference-counted smart pointer) to guarantee there's no use-after-free. But you can't use Rc in no_std, and it seems the best alternative is to annotate references with the 'static lifetime (meaning they will never be freed, so there trivially can't be any use-after-free), even if you don't really need them to have such a long lifetime. It's not ideal but it does work.
Posted May 24, 2022 15:17 UTC (Tue)
by Wol (subscriber, #4433)
[Link] (5 responses)
Or have I completely mis-understood the problem space (not unlikely :-)
Cheers,
Posted May 24, 2022 15:25 UTC (Tue)
by mathstuf (subscriber, #69389)
[Link]
```
But this will always use the maximum size. Note that there are likely perf issues when using something like this that has an `impl Drop` (though maybe `Option<T>` assuming it doesn't take up more room could help elide things there?).
Or course, there is always the possibility that there is some solution that I haven't come across.
Posted May 26, 2022 8:15 UTC (Thu)
by NYKevin (subscriber, #129325)
[Link] (3 responses)
TL;DR: If it's small enough that alloca is safe, you can almost always get away with just using the max size, with little to no downside. If it's large enough that that's a bad idea, then it's also large enough that you can't use alloca and should put it on the heap instead. Therefore, it is not totally unreasonable for a language such as Rust to look at this feature and say "Why bother?"
Posted May 26, 2022 10:09 UTC (Thu)
by farnz (subscriber, #17727)
[Link] (2 responses)
FWIW, in the last 25 years, I've only seen one form of fully justified use of alloca, which is solved by other means in Rust and C++: you use it on "big" systems (ones with stack expansion in the OS) as a way to avoid leaking memory that you know becomes invalid to access at the end of this function. Effectively a way of guaranteeing that regardless of the path through the code, the allocation is freed when no longer in use - which is guaranteed by std::unique_ptr in C++, and by Box in Rust.
Every other use I've seen of alloca has been trivially replaced by a fixed-sized stack allocation - which had the advantage in my embedded days of allowing the compiler to detect statically that we would overflow our stack if we actually used the full allocation.
Posted May 27, 2022 9:00 UTC (Fri)
by NYKevin (subscriber, #129325)
[Link] (1 responses)
According to ulimit -s, on my Debian (WSL) system, the stack size maximum is 8 MiB, which has to accommodate the entire stack and not just your frame. I assume this value corresponds to getrlimit(2)'s RLIMIT_STACK value, but I'm not writing a whole C program just to check. Given that the rlimit is adjustable, it's entirely possible that some sysadmin has decided to give you a hilariously small stack and you can't actually grow, even if the architecture and OS are fully capable of supporting dynamic stack growth. The net effect of this is that alloca is not actually safe unless the object is tiny, or at least reasonably small, and is in no way a good general-purpose substitute for std::unique_ptr or the like. If you want to ensure that all code paths lead to free, you basically have to use gotos, or a language other than C.
Posted May 27, 2022 14:19 UTC (Fri)
by farnz (subscriber, #17727)
[Link]
That's the default, yes, but when you're configuring a system for a use case, you can change it. And that was exactly what I saw done - the system was a "big " system for its era, with 256 MiB RAM, and the rlimit increased to 256 MiB so that actually hitting stack overflow meant you were swapping anyway.
It was also surprisingly portable - at the time (1990s), most system administrators would obey the instruction to increase rlimits to match the software the system was bought to run. Nowadays, I expect most sysadmins would be a lot more cautious about obeying that sort of demand.
Posted May 24, 2022 13:12 UTC (Tue)
by atnot (subscriber, #124910)
[Link] (2 responses)
The only thing Rust does slightly differently is that you can make it insert calls to your free/delete/drop function for you when something is truly (not just probably) no longer reachable. Otherwise things work exactly the same. I guess if you really wanted your code to look more like C, you could add a bunch of explicit drop calls at the end of your functions. But that seems a bit silly.
Posted May 24, 2022 16:00 UTC (Tue)
by excors (subscriber, #95769)
[Link] (1 responses)
I think it's more accurate to put it the other way around: Rust will insert calls to the drop function according to quite simple rules (basically when the variable goes out of scope or has its value replaced, or when the object containing it is dropped, a lot like C++ destructors), and *then* it statically verifies that the object is not reachable through any references after that point (which is complicated and involves lifetimes and the borrow checker). In particular it's the opposite of automatic garbage collection, which first detects the object is definitely unreachable and then drops it.
(It's also worth noting that Rust doesn't guarantee that destructors will ever be called. E.g. std::mem::forget (https://doc.rust-lang.org/std/mem/fn.forget.html) will make an object unreachable without calling the drop function, and it's not an 'unsafe' function because memory leaks don't violate Rust's safety rules. You can also leak objects through Rc cycles etc. That's not common, and you're far less likely to accidentally leak than in C, but it is possible.
In particular it's tempting to design an API like "fn register<'a>(&self, callback: &'a T) -> Handle<'a>" which returns a Handle object whose lifetime cannot exceed the callback object that it was registered with. You might expect that to mean Handle::drop will be called before the callback is destroyed (so you can e.g. store a raw pointer to 'callback' in a global variable, then clear the global in Handle::drop) - and it's true that Handle::drop can't be called after callback is destroyed (the borrow checker will prevent that), but it might not be called at all if it's leaked, in which case the global was never cleared and is now a dangling pointer and violates safety when dereferenced, so you need to redesign your API.)
Posted May 27, 2022 15:06 UTC (Fri)
by tbelaire (subscriber, #141140)
[Link]
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
Wol
Rust 1.61.0 released
let mut _buf = [u8; MAX_SIZE]; // Don't use directly.
let buf = &mut _buf[0..local_size]; // Get a local view into the buffer.
```
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
Rust 1.61.0 released
And a common way to redesign the api is to use an immediately called callback which provides to object you want to restrict, as while it's possible to forget handlers, it is not possible to fail to return. (Well, I guess you can loop forever, but you wouldn't be surprised that the resource is still used then).
From crossbeam's documentation.
Rust 1.61.0 released
let array = [1, 2, 3];
crossbeam::scope(|scope| {
for i in &array {
scope.spawn(move || {
println!("element: {}", i);
});
}
});