As 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&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):
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.
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.
There 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.
Right, 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.
> 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:
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&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):
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.