3 ms·
>(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/
by 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.
- acconsta 11y agoThat code sample is really helpful, thanks. But doesn't that seem kind of redundant? Box and std containers are great as is, except for the one line that aborts on OOM. Would it be possible to use traits to choose OOM behavior at compile time? To be clear, this isn't just important for embedded. A production database or web server should be able to handle an allocation failure without blowing up the whole process. And I'm glad there's some talk about a standard allocator trait. I'll look for the relevant issue.
- dbaupp 11y agoThere could definitely be an alternate method on `Box` that returned Result, but I don't think this is so feasible for containers which allocate in many places and so would require duplicating most of the API, however there may be some tricks that work. https://github.com/rust-lang/rfcs/issues/538 https://github.com/rust-lang/rfcs/issues/538 covers the allocator stuff.
- acconsta 11y agoRight, duplicating every method that allocates also isn't very elegant. But could Box and containers be polymorphic on an OOM handling trait? Alternatively, one could imagine an OOM-safe container that returns results, and a convenience wrapper class that unwraps them. That might harm efficient code generation though.
- mzs 11y agoI really appreciate your excellent insight here, thank you.