5 ms·
1. Yes, as I just said, that's essentially libcore. It doesn't literally offer working dynamic allocations, but I don't see how a cross-platform and cross-use-
by dbaupp 11y ago
1. Yes, as I just said, that's essentially libcore.
It doesn't literally offer working dynamic allocations, but I don't see how a cross-platform and cross-use-case freestanding library can do that, too many different environments/constraints to encode an built-in allocator. In fact, core says nothing about how allocation has to work or even if it needs to exist.
The rustc distribution has the additional `alloc` and `collections` crates layered on top of `core`, which offers some extra functionality when allocation does exist (without assuming other OS support, like IO), and the allocator used is pluggable.
(Do you have an example of a standards complaint C++ freestanding standard library?)
2. There will be some way to do this long term. I can offer no guarantees that it will be what's there right now, but something will exist. This is also something that has been on the radar for a long time, but isn't core to the language. In any case, what is in core is essentially just optimising stack usage, all of the sematics it offers can be done with normal function calls, e.g. `Box::new` is literally just a normal function and perfectly implementable in any environment, if you have some allocation routine (the compiler/language doesn't need to know about either). So the unstable libcore functionality is important and desirable, but not killer.
There's also the meaning of "allocators" as in allowing `Vec` (etc.) to use some other algorithm than what Rust does by default; again something desirable and been on the radar for a while, but not key to the language. I don't think this needs any new language features, just people to explore the library space. (NB. this is different to what libcore offers now, which is basically what is called "placement new" in C++.)
- acconsta 11y ago>(Do you have an example of a standards complaint C++ freestanding standard library?) Yes, you've probably heard of libstdc++: https://gcc.gnu.org/onlinedocs/libstdc++/faq.html#faq.what_is_libsupcxx https://gcc.gnu.org/onlinedocs/libstdc++/faq.html#faq.what_i... In other words, freestanding C++ allows you to to use new and delete normally (after providing malloc and free). Note that this a minimum requirement. There's nothing stopping you from using STL containers in embedded systems, and people do (especially now that STL containers can take user defined allocators). It seems like Rust is trying to achieve the same result with libcore. That's good! Thanks for clarifying. But now you lose all the nice parts of the standard library, and probably much of the safety. If you want to use Box, you have to manually do the allocation, check the pointer, and construct the box. Buffer too small? Forget the pointer check? Welcome to undefined behavior. If you want a container, you have to roll your own. If you want a container that takes user-defined allocators, I'm not even sure what'd you do.
- dbaupp 11y agoAs you say, the libsupc++ requires you to provide malloc and free, just like libcore. > probably much of the safety Only if you want to make life hard for yourself. The standard library is not at all special in its ability to create safe abstractions for `unsafe` code, and doing this makes things so much smoother: you don't have to scatter `unsafe` all over your code, and you let the compiler help you as much as possible. Also, I think there's a lot of nice stuff in `core` too; even string formatting works. You can still implement equally safe version of them tuned for your environment or, more likely (as the ecosystem develops), use a crate that someone else has written that does it for you. Here's the start of an implementation of a version of `Box` that returns `Result` instead of crashing on allocation failure: extern crate core; use core::{mem, ops, ptr}; extern { fn malloc(size: usize) -> *const (); fn free(p: *const ()); } pub struct MyBox<T> { p: *const T, } impl<T> MyBox<T> { fn new(x: T) -> Result<MyBox<T>, ()> { unsafe { let p = malloc(mem::size_of::<T>()) as *const T; if p.is_null() { Err(()) } else { ptr::write(p as *mut T, x); Ok(MyBox { p: p }) } } } } impl<T> Drop for MyBox<T> { fn drop(&mut self) { unsafe { drop(ptr::read(self.p)); free(self.p as *const _); } } } // allow `*` to work impl<T> ops::Deref for MyBox<T> { type Target = T; fn deref(&self) -> &T { unsafe { &*self.p } } } This is a safe interface, and is a simplified version of how the built-in box is defined in `std` (well, `alloc`). I've completed the rest of the core functionality `Box` offers, including a demonstration at the bottom: https://play.rust-lang.org/?gist=77f2b15bcc4273846140&version=nightly https://play.rust-lang.org/?gist=77f2b15bcc4273846140&versio... The whole of Rust's standard library (including `Box`, all the containers, all of IO) is built via this process: wrap the raw `unsafe` functionality into safe interfaces that manage things like checking `malloc`'s return value, bounds checking buffer accesses. > If you want a container that takes user-defined allocators, I'm not even sure what'd you do. Do exactly what C++ does: have some interface (a trait, in Rust) that an allocator needs to satisfy and have the collections take a type parameter that implements that interface. We "already" know what this looks like in Rust (broadly speaking): struct Box<T, A: Allocator = DefaultAllocator> { p: *const T, allocator: A, } The hardest part and sticking point for including this in `std`/`core` is working out a good interface, because it will live forever, needs to cover as much of the problem space as possible and have accommodation for some possible future expansions to the language/library. This isn't such a problem for a container library outside `std`, which is much more flexible because it can be versioned independently, at whatever pace is necessary. An out-of-tree version aimed at a single use-case (e.g. embedded development) is likely even easier, since the problem space is smaller.