mirror of
https://github.com/rust-embedded/heapless.git
synced 2025-09-28 04:50:34 +00:00
Merge #43
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:
commit
f1b58a8d67
@ -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"
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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
35
src/const_fn.rs
Normal 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)*
|
||||
);
|
||||
}
|
38
src/lib.rs
38
src/lib.rs
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
|
20
src/vec.rs
20
src/vec.rs
@ -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;
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user