43: Put all const functions behind a const-fn feature r=japaric a=XOSplicer

## Purpose
This PR introduces the `const-fn` feature gate, which when enabled makes most `new` methods `const` 
and therefore usable for initializing `static` variables.
The idea was introduced in #40 with the purpose to come closer to targeting stable rust.
`const` functions are currently only available on rust nightly (tracking issue: [const fn tracking issue (RFC 911)](https://github.com/rust-lang/rust/issues/24111)).
In order to target stable rust this feature is made opt-in.

This feature is a **breaking change** as users of the library now need to explicitly enable it by changing their `Cargo.toml`:
```
...
[dependencies]
heapless = { version = "0.4.0", features = ["const-fn"] }
...
```



## Approach
The implementation of the feature mainly consist of the `const_fn!` macro, which takes a function with `const` modifier
and removes the modifier if the feature gate is not activated.
For the `const` functions a test is intoduced that checks if `static` variables can be initialized.
These tests are only active if the feature is active.
I have not found a way to make doc-test depend on a feature. Therefore some doc-tests are adapted, so that no static initialization is necessary.
The `ci/script.sh` is adapted to also tests with the `--all-feature` flag

## Future
When in the future the `const_fn` rust feature becomes stable, this feature gate **might become active by default**.


Closes #41 .

Co-authored-by: Felix <stegmaier.felix@gmail.com>
Co-authored-by: Felix Stegmaier <stegmaier.felix@gmail.com>
Co-authored-by: Felix Stegmaier <felix.stegmaier@hpe.com>
This commit is contained in:
bors[bot] 2018-07-13 17:35:29 +00:00
commit f1b58a8d67
12 changed files with 256 additions and 85 deletions

View File

@ -18,6 +18,10 @@ name = "heapless"
repository = "https://github.com/japaric/heapless"
version = "0.3.6"
[features]
default = ["const-fn"]
const-fn = []
[dev-dependencies]
scoped_threadpool = "0.1.8"

View File

@ -5,14 +5,18 @@ main() {
if [ $TARGET = x86_64-unknown-linux-gnu ]; then
cargo test --target $TARGET
cargo test --target $TARGET --no-default-features
cargo test --target $TARGET --release
cargo test --target $TARGET --release --no-default-features
export RUSTFLAGS="-Z sanitizer=thread"
export RUST_TEST_THREADS=1
export TSAN_OPTIONS="suppressions=$(pwd)/blacklist.txt"
cargo test --test tsan --target $TARGET
cargo test --test tsan --target $TARGET --no-default-features
cargo test --test tsan --target $TARGET --release
cargo test --test tsan --target $TARGET --release --no-default-features
fi
}

View File

@ -12,9 +12,11 @@ pub mod mem {
impl<T> ManuallyDrop<T> {
#[inline]
pub const fn new(value: T) -> ManuallyDrop<T> {
ManuallyDrop { value: value }
}
const_fn!(
pub const fn new(value: T) -> ManuallyDrop<T> {
ManuallyDrop { value: value }
}
);
}
impl<T> Deref for ManuallyDrop<T> {
@ -33,13 +35,42 @@ pub mod mem {
}
}
pub const unsafe fn uninitialized<T>() -> T {
#[allow(unions_with_drop_fields)]
union U<T> {
none: (),
some: T,
}
const_fn!(
pub const unsafe fn uninitialized<T>() -> T {
#[allow(unions_with_drop_fields)]
union U<T> {
none: (),
some: T,
}
U { none: () }.some
}
);
}
#[cfg(feature = "const-fn")] // Remove this if there are more tests
#[cfg(test)]
mod test {
use __core;
use __core::mem::ManuallyDrop;
use core;
#[cfg(feature = "const-fn")]
#[test]
fn static_uninitzialized() {
static mut I: i32 = unsafe { __core::mem::uninitialized() };
// Initialize before drop
unsafe { core::ptr::write(&mut I as *mut i32, 42) };
unsafe{ assert_eq!(I, 42) };
}
#[cfg(feature = "const-fn")]
#[test]
fn static_new_manually_drop() {
static mut M: ManuallyDrop<i32> = ManuallyDrop::new(42);
unsafe { assert_eq!(*M, 42); }
// Drop before deinitialization
unsafe { core::ptr::drop_in_place(&mut M as &mut i32 as *mut i32) };
}
U { none: () }.some
}
}

View File

@ -107,21 +107,24 @@ where
K: Kind,
{
/* Constructors */
/// Creates an empty BinaryHeap as a $K-heap.
///
/// ```
/// use heapless::binary_heap::{BinaryHeap, Max};
/// use heapless::consts::*;
///
/// let mut heap: BinaryHeap<_, U8, Max> = BinaryHeap::new();
/// heap.push(4).unwrap();
/// ```
pub const fn new() -> Self {
BinaryHeap {
_kind: PhantomData,
data: Vec::new(),
const_fn!(
/// Creates an empty BinaryHeap as a $K-heap.
///
/// ```
/// use heapless::binary_heap::{BinaryHeap, Max};
/// use heapless::consts::*;
///
/// let mut heap: BinaryHeap<_, U8, Max> = BinaryHeap::new();
/// heap.push(4).unwrap();
/// ```
pub const fn new() -> Self {
BinaryHeap {
_kind: PhantomData,
data: Vec::new(),
}
}
}
);
/* Public API */
/// Returns the capacity of the binary heap.
@ -430,6 +433,12 @@ mod tests {
use binary_heap::{self, BinaryHeap, Min};
use consts::*;
#[cfg(feature = "const-fn")]
#[test]
fn static_new() {
static mut _B: BinaryHeap<i32, U16, Min> = BinaryHeap::new();
}
#[test]
fn min() {
let mut heap = BinaryHeap::<_, U16, Min>::new();

35
src/const_fn.rs Normal file
View File

@ -0,0 +1,35 @@
// Make functions `const` if the `const-fn` feature is active.
// The meta attributes are in place to keep doc comments with the functions.
// The function definition incl. annotations and doc comments must be enclodes
// by the marco invocation.
macro_rules! const_fn {
($(#[$attr:meta])* pub const unsafe fn $($f:tt)*) => (
$(#[$attr])*
#[cfg(feature = "const-fn")]
pub const unsafe fn $($f)*
$(#[$attr])*
#[cfg(not(feature = "const-fn"))]
pub unsafe fn $($f)*
);
($(#[$attr:meta])* pub const fn $($f:tt)*) => (
$(#[$attr])*
#[cfg(feature = "const-fn")]
pub const fn $($f)*
$(#[$attr])*
#[cfg(not(feature = "const-fn"))]
pub fn $($f)*
);
($(#[$attr:meta])* const fn $($f:tt)*) => (
$(#[$attr])*
#[cfg(feature = "const-fn")]
const fn $($f)*
$(#[$attr])*
#[cfg(not(feature = "const-fn"))]
fn $($f)*
);
}

View File

@ -18,7 +18,15 @@
//! assert_eq!(xs.pop(), Some(42));
//!
//! // in a `static` variable
//! static mut XS: Vec<u8, U8> = Vec::new();
//! // static mut XS: Vec<u8, U8> = Vec::new(); // requires feature `const-fn`
//!
//! // work around
//! static mut XS: Option<Vec<u8, U8>> = None;
//! unsafe { XS = Some(Vec::new()) };
//! let xs = unsafe { XS.as_mut().unwrap() };
//!
//! xs.push(42);
//! assert_eq!(xs.pop(), Some(42));
//!
//! // in the heap (though kind of pointless because no reallocation)
//! let mut ys: Box<Vec<u8, U8>> = Box::new(Vec::new());
@ -47,11 +55,34 @@
//! queue
//! - [`String`](struct.String.html)
//! - [`Vec`](struct.Vec.html)
//!
//!
//! In order to target the Rust stable toolchain, there are some feature gates.
//! The features need to be enabled in `Cargo.toml` in order to use them.
//! Once the underlaying features in Rust are stable,
//! these feature gates might be activated by default.
//!
//! Example of `Cargo.toml`:
//!
//! ```text
//! ...
//! [dependencies]
//! heapless = { version = "0.4.0", features = ["const-fn"] }
//! ...
//!
//! ```
//!
//! Currently the following features are availbale and not active by default:
//!
//! - `"const-fn"` -- Enable the nightly `const_fn` feature and make most `new` methods `const`.
//! This way they can be used to initialize static memory at compile time.
//!
#![allow(warnings)]
#![deny(missing_docs)]
#![deny(warnings)]
#![feature(const_fn)]
#![cfg_attr(feature = "const-fn", feature(const_fn))]
#![feature(core_intrinsics)]
#![feature(untagged_unions)]
#![no_std]
@ -61,6 +92,9 @@ extern crate hash32;
#[cfg(test)]
extern crate std;
#[macro_use]
mod const_fn;
pub use binary_heap::BinaryHeap;
pub use generic_array::typenum::consts;
pub use generic_array::ArrayLength;

View File

@ -21,19 +21,22 @@ where
N: ArrayLength<(K, V)>,
K: Eq,
{
/// Creates an empty `LinearMap`
///
/// # Examples
///
/// ```
/// use heapless::LinearMap;
/// use heapless::consts::*;
///
/// let mut map: LinearMap<&str, isize, U8> = LinearMap::new();
/// ```
pub const fn new() -> Self {
LinearMap { buffer: Vec::new() }
}
const_fn!(
/// Creates an empty `LinearMap`
///
/// # Examples
///
/// ```
/// use heapless::LinearMap;
/// use heapless::consts::*;
///
/// let mut map: LinearMap<&str, isize, U8> = LinearMap::new();
/// ```
pub const fn new() -> Self {
LinearMap { buffer: Vec::new() }
}
);
/// Returns the number of elements that the map can hold
///
@ -439,3 +442,18 @@ where
self.iter.next().map(|&mut (ref k, ref mut v)| (k, v))
}
}
#[cfg(feature = "const-fn")] // Remove this if there are more tests
#[cfg(test)]
mod test {
use consts::*;
use LinearMap;
#[cfg(feature = "const-fn")]
#[test]
fn static_new() {
static mut _L: LinearMap<i32, i32, U8>= LinearMap::new();
}
}

View File

@ -61,11 +61,13 @@ impl<U> Atomic<U>
where
U: Uxx,
{
const fn new(v: U) -> Atomic<U> {
Atomic {
v: UnsafeCell::new(v),
const_fn!(
const fn new(v: U) -> Atomic<U> {
Atomic {
v: UnsafeCell::new(v),
}
}
}
);
fn get_mut(&mut self) -> &mut U {
unsafe { &mut *self.v.get() }
@ -116,13 +118,16 @@ where
/// use heapless::RingBuffer;
/// use heapless::consts::*;
///
/// static mut RB: RingBuffer<Event, U4> = RingBuffer::new();
/// // static mut RB: RingBuffer<Event, U4> = RingBuffer::new(); // requires feature `const-fn`
///
/// static mut RB: Option<RingBuffer<Event, U4>> = None;
///
/// enum Event { A, B }
///
/// fn main() {
/// unsafe { RB = Some(RingBuffer::new()) };
/// // NOTE(unsafe) beware of aliasing the `consumer` end point
/// let mut consumer = unsafe { RB.split().1 };
/// let mut consumer = unsafe { RB.as_mut().unwrap().split().1 };
///
/// loop {
/// // `dequeue` is a lockless operation
@ -138,7 +143,7 @@ where
/// // this is a different execution context that can preempt `main`
/// fn interrupt_handler() {
/// // NOTE(unsafe) beware of aliasing the `producer` end point
/// let mut producer = unsafe { RB.split().0 };
/// let mut producer = unsafe { RB.as_mut().unwrap().split().0 };
/// # let condition = true;
///
/// // ..
@ -264,14 +269,17 @@ macro_rules! impl_ {
N: Add<U1> + Unsigned,
Sum<N, U1>: ArrayLength<T>,
{
/// Creates an empty ring buffer with a fixed capacity of `N`
pub const fn $uxx() -> Self {
RingBuffer {
buffer: ManuallyDrop::new(unsafe { mem::uninitialized() }),
head: Atomic::new(0),
tail: Atomic::new(0),
const_fn!(
/// Creates an empty ring buffer with a fixed capacity of `N`
pub const fn $uxx() -> Self {
RingBuffer {
buffer: ManuallyDrop::new(unsafe { mem::uninitialized() }),
head: Atomic::new(0),
tail: Atomic::new(0),
}
}
}
);
/// Returns the item in the front of the queue, or `None` if the queue is empty
pub fn dequeue(&mut self) -> Option<T> {
@ -349,10 +357,13 @@ where
N: Add<U1> + Unsigned,
Sum<N, U1>: ArrayLength<T>,
{
/// Alias for [`RingBuffer::usize`](struct.RingBuffer.html#method.usize)
pub const fn new() -> Self {
RingBuffer::usize()
}
const_fn!(
/// Alias for [`RingBuffer::usize`](struct.RingBuffer.html#method.usize)
pub const fn new() -> Self {
RingBuffer::usize()
}
);
}
impl_!(u8);
@ -434,6 +445,12 @@ mod tests {
use consts::*;
use RingBuffer;
#[cfg(feature = "const-fn")]
#[test]
fn static_new() {
static mut _R: RingBuffer<i32, U4> = RingBuffer::new();
}
#[test]
fn drop() {
struct Droppable;

View File

@ -207,9 +207,9 @@ mod tests {
#[test]
fn sanity() {
static mut RB: RingBuffer<i32, U2> = RingBuffer::new();
let mut rb: RingBuffer<i32, U2> = RingBuffer::new();
let (mut p, mut c) = unsafe { RB.split() };
let (mut p, mut c) = rb.split();
assert_eq!(c.dequeue(), None);

View File

@ -17,22 +17,25 @@ impl<N> String<N>
where
N: ArrayLength<u8>,
{
/// Constructs a new, empty `String` with a fixed capacity of `N`
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use heapless::String;
/// use heapless::consts::*;
///
/// let mut s: String<U4> = String::new();
/// ```
#[inline]
pub const fn new() -> Self {
String { vec: Vec::new() }
}
const_fn!(
/// Constructs a new, empty `String` with a fixed capacity of `N`
///
/// # Examples
///
/// Basic usage:
///
/// ```
/// use heapless::String;
/// use heapless::consts::*;
///
/// let mut s: String<U4> = String::new();
/// ```
pub const fn new() -> Self {
String { vec: Vec::new() }
}
);
/// Converts a vector of bytes into a `String`.
///
@ -521,6 +524,12 @@ mod tests {
use consts::*;
use {String, Vec};
#[cfg(feature = "const-fn")]
#[test]
fn static_new() {
static mut _S: String<U8> = String::new();
}
#[test]
fn debug() {
extern crate std;

View File

@ -48,13 +48,15 @@ where
N: ArrayLength<T>,
{
/* Constructors */
/// Constructs a new, empty vector with a fixed capacity of `N`
pub const fn new() -> Self {
Vec {
buffer: ManuallyDrop::new(unsafe { mem::uninitialized() }),
len: 0,
const_fn!(
/// Constructs a new, empty vector with a fixed capacity of `N`
pub const fn new() -> Self {
Vec {
buffer: ManuallyDrop::new(unsafe { mem::uninitialized() }),
len: 0,
}
}
}
);
/* Public API */
/// Returns the maximum number of elements the vector can hold
@ -512,6 +514,12 @@ mod tests {
use consts::*;
use Vec;
#[cfg(feature = "const-fn")]
#[test]
fn static_new() {
static mut _V: Vec<i32, U4> = Vec::new();
}
macro_rules! droppable {
() => (
struct Droppable;

View File

@ -13,9 +13,10 @@ use scoped_threadpool::Pool;
#[test]
fn once() {
static mut RB: RingBuffer<i32, U4> = RingBuffer::new();
static mut RB: Option<RingBuffer<i32, U4>> = None;
unsafe{ RB = Some(RingBuffer::new()) };
let rb = unsafe { &mut RB };
let rb = unsafe { RB.as_mut().unwrap() };
rb.enqueue(0).unwrap();
@ -34,9 +35,10 @@ fn once() {
#[test]
fn twice() {
static mut RB: RingBuffer<i32, U8> = RingBuffer::new();
static mut RB: Option<RingBuffer<i32, U4>> = None;
unsafe{ RB = Some(RingBuffer::new()) };
let rb = unsafe { &mut RB };
let rb = unsafe { RB.as_mut().unwrap() };
rb.enqueue(0).unwrap();
rb.enqueue(1).unwrap();