diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..ba5d210 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,23 @@ +language: rust +matrix: + include: + - rust: stable + env: FEATURES='use_std' + - rust: beta + env: FEATURES='use_std' + - rust: nightly + env: FEATURES='' + - rust: nightly + env: FEATURES='use_alloc' + - rust: nightly + env: FEATURES='use_collections' + - rust: nightly + env: FEATURES='use_alloc use_collections' +script: + - cargo build --features "$FEATURES" +notifications: + irc: + channels: + - "chat.freenode.net#m-labs" + use_notice: true + skip_join: true diff --git a/Cargo.toml b/Cargo.toml index 25b644e..db34b8a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,17 @@ [package] name = "managed" -description = "Placeholder" -version = "0.0.0" +version = "0.1.0" authors = ["whitequark "] +description = "An interface for logically owning objects, whether or not heap allocation is available." +documentation = "https://docs.rs/managed/" +homepage = "https://github.com/m-labs/rust-managed" +repository = "https://github.com/m-labs/rust-managed.git" +readme = "README.md" +keywords = ["ownership"] license = "0BSD" -[dependencies] +[features] +use_std = [] +use_alloc = [] +use_collections = [] +default = ["use_std"] diff --git a/LICENSE-0BSD.txt b/LICENSE-0BSD.txt new file mode 100644 index 0000000..dbf1d91 --- /dev/null +++ b/LICENSE-0BSD.txt @@ -0,0 +1,12 @@ +Copyright (C) 2017 whitequark@whitequark.org + +Permission to use, copy, modify, and/or distribute this software for +any purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT +OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/README.md b/README.md index 618bd19..0a92779 100644 --- a/README.md +++ b/README.md @@ -1 +1,114 @@ -See https://lab.whitequark.org/notes/2016-12-17/owning-collections-in-heap-less-rust/ +Managed +======= + +_managed_ is a library that provides a way to logically own objects, whether or not +heap allocation is available. + +Motivation +---------- + +The _managed_ library exists at the intersection of three concepts: _heap-less environments_, +_collections_ and _generic code_. Consider this struct representing a network interface: + +```rust +pub struct Interface<'a, 'b: 'a, + DeviceT: Device, + ProtocolAddrsT: BorrowMut<[IpAddress]>, + SocketsT: BorrowMut<[Socket<'a, 'b>]> +> { + device: DeviceT, + hardware_addr: EthernetAddress, + protocol_addrs: ProtocolAddrsT, + sockets: SocketsT, + phantom: PhantomData> +} +``` + +There are three things the struct `Interface` is parameterized over: + * an object implementing the trait `DeviceT`, which it owns; + * a slice of `IPAddress`es, which it either owns or borrows mutably; + * a slice of `Socket`s, which it either owns or borrows mutably, and which further either + own or borrow some memory. + +The motivaion for using `BorrowMut` is that in environments with heap, the struct ought to +own a `Vec`; on the other hand, without heap there is neither `Vec` nor `Box`, and it is only +possible to use a `&mut`. Both of these implement BorrowMut. + +Note that owning a `BorrowMut` in this way does not hide the concrete type inside `BorrowMut`; +if the slice is backed by a `Vec` then the `Vec` may still be resized by external code, +although not the implementation of `Interface`. + +In isolation, this struct is easy to use. However, when combined with another codebase, perhaps +embedded in a scheduler, problems arise. The type parameters have to go somewhere! There +are two choices: + * either the type parameters, whole lot of them, infect the scheduler and push ownership + even higher in the call stack (self-mutably-borrowing structs are not usable in safe Rust, + so the scheduler could not easily own the slices); + * or the interface is owned as a boxed trait object, excluding heap-less systems. + +Clearly, both options are unsatisfying. Enter _managed_! + +Installation +------------ + +To use the _managed_ library in your project, add the following to `Cargo.toml`: + +```toml +[dependencies] +managed = "0.1" +``` + +The default configuration assumes a hosted environment, for ease of evaluation. +You probably want to disable default features and configure them one by one: + +```toml +[dependencies] +managed = { version = "...", default-features = false, features = ["..."] } +``` + +### Feature `use_std` + +The `use_std` feature enables use of `Box` and `Vec` through a dependency on the `std` crate. + +### Feature `use_alloc` + +The `use_alloc` feature enables use of `Box` through a dependency on the `alloc` crate. +This only works on nightly rustc. + +### Feature `use_collections` + +The `use_collections` feature enables use of `Vec` through a dependency on +the `collections` crate. This only works on nightly rustc. + +Usage +----- + +_managed_ is an interoperability crate: it does not include complex functionality but rather +defines an interface that may be used by many downstream crates. It includes two enums: + +```rust +pub enum Managed<'a, T: 'a + ?Sized> { + Borrowed(&'a mut T), + #[cfg(/* Box available */)] + Owned(Box), +} + +pub enum ManagedSlice<'a, T: 'a> { + Borrow(&'a mut [T]), + #[cfg(/* Vec available */)] + Owned(Vec) +} +``` + +The enums have the `From` implementations from the corresponding types, and `Deref`/`DerefMut` +implementations to the type `T`, as well as other helper methods; see the [full documentation][doc] +for details. + +Of course, the enums can be always matched explicitly as well. + +License +------- + +_managed_ is distributed under the terms of 0-clause BSD license. + +See [LICENSE-0BSD](LICENSE-0BSD.txt) for details. diff --git a/src/lib.rs b/src/lib.rs index cdfbe1a..2641246 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,16 @@ -#[cfg(test)] -mod tests { - #[test] - fn it_works() { - } -} +#![no_std] +#![cfg_attr(feature = "use_alloc", feature(alloc))] +#![cfg_attr(feature = "use_collections", feature(collections))] + +#[cfg(feature = "use_std")] +extern crate std; +#[cfg(feature = "use_alloc")] +extern crate alloc; +#[cfg(feature = "use_collections")] +extern crate collections; + +mod object; +mod slice; + +pub use object::Managed; +pub use slice::ManagedSlice; diff --git a/src/object.rs b/src/object.rs new file mode 100644 index 0000000..137a977 --- /dev/null +++ b/src/object.rs @@ -0,0 +1,91 @@ +use core::ops::{Deref, DerefMut}; +use core::fmt; + +#[cfg(feature = "use_std")] +use std::boxed::Box; +#[cfg(feature = "use_alloc")] +use alloc::boxed::Box; +#[cfg(feature = "use_std")] +use std::vec::Vec; +#[cfg(all(feature = "use_alloc", feature = "use_collections"))] +use collections::vec::Vec; + +/// A managed object. +/// +/// This enum can be used to represent exclusive access to objects. In Rust, exclusive access +/// to an object is obtained by either owning the object, or owning a mutable pointer +/// to the object; hence, "managed". +/// +/// The purpose of this enum is providing good ergonomics with `std` present while making +/// it possible to avoid having a heap at all (which of course means that `std` is not present). +/// To achieve this, the variants other than `Borrow` are only available when the corresponding +/// feature is opted in. +/// +/// A function that requires a managed object should be generic over an `Into>` +/// argument; then, it will be possible to pass either a `Box`, `Vec`, or a `&'a mut T` +/// without any conversion at the call site. +/// +/// Note that a `Vec` converted into an `Into>` gets transformed +/// into a boxed slice, and can no longer be resized. See also +/// [ManagedSlice][struct.ManagedSlice.html], which does not have this drawback. +pub enum Managed<'a, T: 'a + ?Sized> { + /// Borrowed variant. + Borrowed(&'a mut T), + /// Owned variant, only available with the `use_std` or `use_alloc` feature enabled. + #[cfg(any(feature = "use_std", feature = "use_alloc"))] + Owned(Box) +} + +impl<'a, T: 'a + ?Sized> fmt::Debug for Managed<'a, T> + where T: fmt::Debug { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &Managed::Borrowed(ref x) => write!(f, "Borrowed({:?})", x), + #[cfg(any(feature = "use_std", feature = "use_alloc"))] + &Managed::Owned(ref x) => write!(f, "Owned({:?})", x) + } + + } +} + +impl<'a, T: 'a + ?Sized> From<&'a mut T> for Managed<'a, T> { + fn from(value: &'a mut T) -> Self { + Managed::Borrowed(value) + } +} + +#[cfg(any(feature = "use_std", feature = "use_alloc"))] +impl From> for Managed<'static, T> { + fn from(value: Box) -> Self { + Managed::Owned(value) + } +} + +#[cfg(any(feature = "use_std", all(feature = "use_alloc", feature = "use_collections")))] +impl From> for Managed<'static, [T]> { + fn from(value: Vec) -> Self { + Managed::Owned(value.into_boxed_slice()) + } +} + +impl<'a, T: 'a + ?Sized> Deref for Managed<'a, T> { + type Target = T; + + fn deref(&self) -> &Self::Target { + match self { + &Managed::Borrowed(ref value) => value, + #[cfg(any(feature = "use_std", feature = "use_alloc"))] + &Managed::Owned(ref value) => value + } + } +} + +impl<'a, T: 'a + ?Sized> DerefMut for Managed<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + &mut Managed::Borrowed(ref mut value) => value, + #[cfg(any(feature = "use_std", feature = "use_alloc"))] + &mut Managed::Owned(ref mut value) => value + } + } +} diff --git a/src/slice.rs b/src/slice.rs new file mode 100644 index 0000000..dc3c92c --- /dev/null +++ b/src/slice.rs @@ -0,0 +1,78 @@ +use core::ops::{Deref, DerefMut}; +use core::fmt; + +#[cfg(feature = "use_std")] +use std::vec::Vec; +#[cfg(feature = "use_collections")] +use collections::vec::Vec; + +/// A managed slice. +/// +/// This enum can be used to represent exclusive access to slices of objects. +/// In Rust, exclusive access to an object is obtained by either owning the object, +/// or owning a mutable pointer to the object; hence, "managed". +/// +/// The purpose of this enum is providing good ergonomics with `std` present while making +/// it possible to avoid having a heap at all (which of course means that `std` is not present). +/// To achieve this, the variants other than `Borrow` are only available when the corresponding +/// feature is opted in. +/// +/// A function that requires a managed object should be generic over an `Into>` +/// argument; then, it will be possible to pass either a `Vec`, or a `&'a mut [T]` +/// without any conversion at the call site. +/// +/// See also [Managed][struct.Managed.html]. +pub enum ManagedSlice<'a, T: 'a> { + /// Borrowed variant. + Borrowed(&'a mut [T]), + /// Owned variant, only available with the `use_std` or `use_collections` feature enabled. + #[cfg(any(feature = "use_std", feature = "use_collections"))] + Owned(Vec) +} + +impl<'a, T: 'a> fmt::Debug for ManagedSlice<'a, T> + where T: fmt::Debug { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + &ManagedSlice::Borrowed(ref x) => write!(f, "Borrowed({:?})", x), + #[cfg(any(feature = "use_std", feature = "use_collections"))] + &ManagedSlice::Owned(ref x) => write!(f, "Owned({:?})", x) + } + + } +} + +impl<'a, T: 'a> From<&'a mut [T]> for ManagedSlice<'a, T> { + fn from(value: &'a mut [T]) -> Self { + ManagedSlice::Borrowed(value) + } +} + +#[cfg(any(feature = "use_std", feature = "use_collections"))] +impl From> for ManagedSlice<'static, T> { + fn from(value: Vec) -> Self { + ManagedSlice::Owned(value) + } +} + +impl<'a, T: 'a> Deref for ManagedSlice<'a, T> { + type Target = [T]; + + fn deref(&self) -> &Self::Target { + match self { + &ManagedSlice::Borrowed(ref value) => value, + #[cfg(any(feature = "use_std", feature = "use_collections"))] + &ManagedSlice::Owned(ref value) => value + } + } +} + +impl<'a, T: 'a> DerefMut for ManagedSlice<'a, T> { + fn deref_mut(&mut self) -> &mut Self::Target { + match self { + &mut ManagedSlice::Borrowed(ref mut value) => value, + #[cfg(any(feature = "use_std", feature = "use_collections"))] + &mut ManagedSlice::Owned(ref mut value) => value + } + } +}