Implement.

This commit is contained in:
whitequark 2017-01-07 18:28:51 +00:00
parent a816d0d4c2
commit b1e24a9826
7 changed files with 346 additions and 10 deletions

23
.travis.yml Normal file
View File

@ -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

View File

@ -1,8 +1,17 @@
[package]
name = "managed"
description = "Placeholder"
version = "0.0.0"
version = "0.1.0"
authors = ["whitequark <whitequark@whitequark.org>"]
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"]

12
LICENSE-0BSD.txt Normal file
View File

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

115
README.md
View File

@ -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<Socket<'a, 'b>>
}
```
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<T>),
}
pub enum ManagedSlice<'a, T: 'a> {
Borrow(&'a mut [T]),
#[cfg(/* Vec available */)]
Owned(Vec<T>)
}
```
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.

View File

@ -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;

91
src/object.rs Normal file
View File

@ -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<Managed<'a, T>>`
/// argument; then, it will be possible to pass either a `Box<T>`, `Vec<T>`, or a `&'a mut T`
/// without any conversion at the call site.
///
/// Note that a `Vec<T>` converted into an `Into<Managed<'static, [T]>>` 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<T>)
}
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<T: ?Sized + 'static> From<Box<T>> for Managed<'static, T> {
fn from(value: Box<T>) -> Self {
Managed::Owned(value)
}
}
#[cfg(any(feature = "use_std", all(feature = "use_alloc", feature = "use_collections")))]
impl<T: 'static> From<Vec<T>> for Managed<'static, [T]> {
fn from(value: Vec<T>) -> 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
}
}
}

78
src/slice.rs Normal file
View File

@ -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<ManagedSlice<'a, T>>`
/// argument; then, it will be possible to pass either a `Vec<T>`, 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<T>)
}
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<T: 'static> From<Vec<T>> for ManagedSlice<'static, T> {
fn from(value: Vec<T>) -> 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
}
}
}