|
|
Subscribe / Log in / New account

Rust 1.61.0 released

Rust 1.61.0 released

Posted May 24, 2022 10:49 UTC (Tue) by ballombe (subscriber, #9523)
In reply to: Rust 1.61.0 released by excors
Parent article: Rust 1.61.0 released

Is there a usable C-like subset of rust (without any automatic memory allocation) ?


to post comments

Rust 1.61.0 released

Posted May 24, 2022 11:14 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

There is `#[no_std]` which takes out the `alloc` crate today AFAIK. You're left with what is in `core`. A lot of "nice things" are missing, but it's still not the free-for-all of C (not sure if you're looking for that or not).

Rust 1.61.0 released

Posted May 24, 2022 12:49 UTC (Tue) by excors (subscriber, #95769) [Link] (6 responses)

I'm not entirely sure what you mean by "automatic memory allocation"? In C, automatic (in the context of "automatic storage duration") means objects allocated on the stack, and I think it's impractical to avoid the stack in either C or Rust.

(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.

Rust 1.61.0 released

Posted May 24, 2022 15:17 UTC (Tue) by Wol (subscriber, #4433) [Link] (5 responses)

Provided an item doesn't outlive the function that creates it, does Rust have the equivalent of alloca? Create it in the local stack of the function that wants it, and it goes out of scope with said function?

Or have I completely mis-understood the problem space (not unlikely :-)

Cheers,
Wol

Rust 1.61.0 released

Posted May 24, 2022 15:25 UTC (Tue) by mathstuf (subscriber, #69389) [Link]

The default is to put things on the stack. Or do you mean being able to do something like `[u8; some_local_size]`? I don't think that's possible (the size must be constant evaluated). I think the closest you can get is:

```
let mut _buf = [u8; MAX_SIZE]; // Don't use directly.
let buf = &mut _buf[0..local_size]; // Get a local view into the buffer.
```

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.

Rust 1.61.0 released

Posted May 26, 2022 8:15 UTC (Thu) by NYKevin (subscriber, #129325) [Link] (3 responses)

alloca is formally unsafe, unless you can statically prove that the size you are allocating is "reasonable" for some implementation-defined value of "reasonable." But then you can just use a regular stack allocation of the maximum possible size, which should be cheap or free since that size is "reasonable" and all you're really doing is bumping the stack pointer a bit. The downside, I suppose, is that you ephemerally use some extra memory on the stack, which might result in your stack growing larger than it strictly needs to be (assuming your OS dynamically grows the stack, which AIUI is standard on all modern OS's). But that's a wildly fictional concern, because an alloca as small as a few megabytes can easily blow out the stack all by itself. Relative to the system's memory as a whole, we're talking about very small allocations in the first place, and you're not realistically going to care if your stack is a few hundred KiB larger than it's "supposed to" be, unless you're working on some hilariously resource-constrained embedded device (at which point you probably don't even have an OS in the first place), or you're doing something ridiculous like spawning thousands of threads or implementing golang.

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?"

Rust 1.61.0 released

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.

Rust 1.61.0 released

Posted May 27, 2022 9:00 UTC (Fri) by NYKevin (subscriber, #129325) [Link] (1 responses)

> "big" systems (ones with stack expansion in the OS)

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.

Rust 1.61.0 released

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.

Rust 1.61.0 released

Posted May 24, 2022 13:12 UTC (Tue) by atnot (subscriber, #124910) [Link] (2 responses)

I'm not quite sure what that would entail, Rust doesn't really have "automatic memory allocation" much more than C does. You call some function, by convention called new/init/create to get a new object. This function might or might not call another function named malloc behind your back. When you're done, you call some function named free/delete/drop to clean it up.

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.

Rust 1.61.0 released

Posted May 24, 2022 16:00 UTC (Tue) by excors (subscriber, #95769) [Link] (1 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.

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.)

Rust 1.61.0 released

Posted May 27, 2022 15:06 UTC (Fri) by tbelaire (subscriber, #141140) [Link]

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.
    let array = [1, 2, 3];

    crossbeam::scope(|scope| {
        for i in &array {
            scope.spawn(move || {
                println!("element: {}", i);
            });
        }
    });


Copyright © 2025, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds