Skip to content

Commit dfeeead

Browse files
authored
Merge pull request #31 from orxfun/no-std-support
no-std-support
2 parents 9021ec8 + eb9d992 commit dfeeead

23 files changed

+512
-119
lines changed

Cargo.toml

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
[package]
22
name = "orx-imp-vec"
3-
version = "2.6.0"
3+
version = "2.7.0"
44
edition = "2021"
55
authors = ["orxfun <orx.ugur.arikan@gmail.com>"]
6-
description = "`ImpVec`, standing for immutable push vector 👿, is a data structure which allows appending elements with a shared reference."
6+
description = "`ImpVec`, stands for immutable push vector 👿, is a data structure which allows appending elements with a shared reference."
77
license = "MIT"
88
repository = "https://github.com/orxfun/orx-imp-vec/"
99
keywords = ["vec", "pinned", "bag", "container", "split"]
10-
categories = ["data-structures", "rust-patterns"]
10+
categories = ["data-structures", "rust-patterns", "no-std"]
1111

1212
[dependencies]
13-
orx-pseudo-default = "1.2"
14-
orx-pinned-vec = "3.3"
15-
orx-fixed-vec = "3.3"
16-
orx-split-vec = "3.3"
13+
orx-pseudo-default = { version = "1.4", default-features = false }
14+
orx-pinned-vec = "3.9"
15+
orx-fixed-vec = "3.9"
16+
orx-split-vec = "3.9"

README.md

Lines changed: 190 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,201 @@
33
[![orx-imp-vec crate](https://img.shields.io/crates/v/orx-imp-vec.svg)](https://crates.io/crates/orx-imp-vec)
44
[![orx-imp-vec documentation](https://docs.rs/orx-imp-vec/badge.svg)](https://docs.rs/orx-imp-vec)
55

6-
`ImpVec`, standing for immutable push vector 👿, is a data structure which allows appending elements with a shared reference.
6+
`ImpVec`, stands for immutable push vector 👿, is a data structure which allows appending elements with a shared reference.
77

88
Specifically, it extends vector capabilities with the following two methods:
9-
* `fn imp_push(&self, value: T)`
10-
* `fn imp_extend_from_slice(&self, slice: &[T])`
9+
* [`fn imp_push(&self, value: T)`](https://docs.rs/orx-imp-vec/latest/orx_imp_vec/struct.ImpVec.html#method.imp_push)
10+
* [`fn imp_extend_from_slice(&self, slice: &[T])`](https://docs.rs/orx-imp-vec/latest/orx_imp_vec/struct.ImpVec.html#method.imp_extend_from_slice)
1111

1212
Note that both of these methods can be called with `&self` rather than `&mut self`.
1313

14-
# Motivation
14+
## Motivation
1515

16-
Appending to a vector with a shared reference sounds unconventional, and it is. However, if we consider our vector as a bag of or a container of things rather than having a collective meaning; then, appending element or elements to the end of the vector:
17-
* does not mutate any of already added elements, and hence,
18-
* **it is not different than creating a new element in the scope**.
16+
Appending to a vector with a shared reference sounds unconventional, and it is.
1917

20-
# Safety
18+
From another perspective, however, appending an element to the end of the vector does not mutate any of already added elements or change their positions. It can be argued that *it is not different than creating a new element within the scope*. This statement will be clear with the following example.
19+
20+
The challenge is to define a type-safe, recursive and expressive expression builder. In our toy example, an expression can either be a symbol, or addition or subtraction of two expressions. The final desired ergonomic solution is as follows:
21+
22+
```rust ignore
23+
let scope = Scope::default();
24+
25+
// instantiate some symbols
26+
let x = scope.symbol("x");
27+
let y = scope.symbol("y");
28+
assert_eq!(&x.to_string(), "x");
29+
assert_eq!(&y.to_string(), "y");
30+
31+
// apply binary operations to create new symbols
32+
let p = x + y;
33+
assert_eq!(&p.to_string(), "x + y");
34+
35+
let q = x - y;
36+
assert_eq!(&q.to_string(), "x - y");
37+
38+
// and further binary operations
39+
let t = p + q;
40+
assert_eq!(&t.to_string(), "x + y + x - y");
41+
42+
// we only use 'scope' to create symbols
43+
// but in the background, all expressions are collected in our scope
44+
let all_expressions: Vec<_> = scope.expressions.iter().map(|x| x.to_string()).collect();
45+
assert_eq!(
46+
all_expressions,
47+
["x", "y", "x + y", "x - y", "x + y + x - y"]
48+
);
49+
```
50+
51+
This at first seemed **impossible in safe rust** for way too many reasons. However, it is conveniently possible using an `ImpVec`. You may run the example in [expressions.rs](https://github.com/orxfun/orx-imp-vec/blob/main/examples/expressions.rs) by `cargo run --example expressions`, or see the details of the implementation below.
52+
53+
<details>
54+
<summary style="font-weight:bold;">Complete Implementation</summary>
55+
56+
```rust
57+
use orx_imp_vec::*;
58+
use std::{
59+
fmt::Display,
60+
ops::{Add, Sub},
61+
};
62+
63+
/// A scope for expressions.
64+
#[derive(Default)]
65+
struct Scope<'a> {
66+
expressions: ImpVec<Expr<'a>>,
67+
}
68+
69+
impl<'a> Scope<'a> {
70+
/// Bottom of the expressions recursion, the symbol primitive
71+
fn symbol(&'a self, name: &'static str) -> ExprInScope<'a> {
72+
let expr = Expr::Symbol(name);
73+
self.expressions.imp_push(expr);
74+
ExprInScope {
75+
scope: self,
76+
expr: &self.expressions[self.expressions.len() - 1],
77+
}
78+
}
79+
}
80+
81+
/// A recursive expression with three demo variants
82+
enum Expr<'a> {
83+
Symbol(&'static str),
84+
Addition(&'a Expr<'a>, &'a Expr<'a>),
85+
Subtraction(&'a Expr<'a>, &'a Expr<'a>),
86+
}
87+
88+
impl<'a> Display for Expr<'a> {
89+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
90+
match self {
91+
Expr::Symbol(x) => write!(f, "{}", x),
92+
Expr::Addition(x, y) => write!(f, "{} + {}", x, y),
93+
Expr::Subtraction(x, y) => write!(f, "{} - {}", x, y),
94+
}
95+
}
96+
}
97+
98+
/// Expression in a scope:
99+
/// * it knows what it is
100+
/// * it knows which scope it belongs to
101+
///
102+
/// It can implement Copy which turns out to be extremely important!
103+
#[derive(Clone, Copy)]
104+
struct ExprInScope<'a> {
105+
scope: &'a Scope<'a>,
106+
expr: &'a Expr<'a>,
107+
}
108+
109+
impl<'a> ExprInScope<'a> {
110+
/// Recall, it knows that scope it belongs to,
111+
/// and can check it in O(1)
112+
fn belongs_to_same_scope(&self, other: Self) -> bool {
113+
let self_scope = self.scope as *const Scope;
114+
let other_scope = other.scope as *const Scope;
115+
self_scope == other_scope
116+
}
117+
}
118+
impl<'a> Display for ExprInScope<'a> {
119+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
120+
write!(f, "{}", self.expr)
121+
}
122+
}
123+
124+
impl<'a> Add for ExprInScope<'a> {
125+
type Output = ExprInScope<'a>;
126+
127+
/// We can create an expression by adding two expressions
128+
///
129+
/// Where do we store the new expression?
130+
///
131+
/// Of course, in the scope that both expressions belong to.
132+
/// And we can do so by `imp_push`.
133+
///
134+
/// # Panics
135+
///
136+
/// Panics if the lhs & rhs do not belong to the same scope.
137+
fn add(self, rhs: Self) -> Self::Output {
138+
assert!(self.belongs_to_same_scope(rhs));
139+
let expressions = &self.scope.expressions;
140+
let expr = Expr::Addition(self.expr, rhs.expr);
141+
expressions.imp_push(expr);
142+
ExprInScope {
143+
scope: self.scope,
144+
expr: &expressions[expressions.len() - 1],
145+
}
146+
}
147+
}
148+
149+
impl<'a> Sub for ExprInScope<'a> {
150+
type Output = ExprInScope<'a>;
151+
152+
/// Similarly, we can create an expression by subtracting two expressions
153+
///
154+
/// # Panics
155+
///
156+
/// Panics if the lhs & rhs do not belong to the same scope.
157+
fn sub(self, rhs: Self) -> Self::Output {
158+
assert!(self.belongs_to_same_scope(rhs));
159+
let expressions = &self.scope.expressions;
160+
let expr = Expr::Subtraction(self.expr, rhs.expr);
161+
expressions.imp_push(expr);
162+
ExprInScope {
163+
scope: self.scope,
164+
expr: &expressions[expressions.len() - 1],
165+
}
166+
}
167+
}
168+
169+
let scope = Scope::default();
170+
171+
// instantiate some symbols
172+
let x = scope.symbol("x");
173+
let y = scope.symbol("y");
174+
assert_eq!(&x.to_string(), "x");
175+
assert_eq!(&y.to_string(), "y");
176+
177+
// apply binary operations to create new symbols
178+
let p = x + y;
179+
assert_eq!(&p.to_string(), "x + y");
180+
181+
let q = x - y;
182+
assert_eq!(&q.to_string(), "x - y");
183+
184+
// and further binary operations
185+
let t = p + q;
186+
assert_eq!(&t.to_string(), "x + y + x - y");
187+
188+
// we only use 'scope' to create symbols
189+
// but in the background, all expressions are collected in our scope
190+
let all_expressions: Vec<_> = scope.expressions.iter().map(|x| x.to_string()).collect();
191+
assert_eq!(
192+
all_expressions,
193+
["x", "y", "x + y", "x - y", "x + y + x - y"]
194+
);
195+
```
196+
197+
</details>
198+
199+
200+
## Safety
21201

22202
It is natural to expect that appending elements to a vector does not affect already added elements. However, this is usually not the case due to underlying memory management. For instance, `std::vec::Vec` may move already added elements to different memory locations to maintain the contagious layout of the vector.
23203

@@ -37,10 +217,10 @@ vec.push(4);
37217
// assert_eq!(ref_to_first, &0);
38218
```
39219

40-
This beloved feature of the borrow checker of rust is not required and used for `imp_push` and `imp_extend_from_slice` methods of `ImpVec` since these methods do not require a `&mut self` reference. Therefore, the following code compiles and runs perfectly safely.
220+
This beloved feature of the borrow checker of rust is not required for `imp_push` and `imp_extend_from_slice` methods of `ImpVec` since these methods do not require a `&mut self` reference. Therefore, the following code compiles and runs perfectly safely.
41221

42222
```rust
43-
use orx_imp_vec::prelude::*;
223+
use orx_imp_vec::*;
44224

45225
let mut vec = ImpVec::new();
46226
vec.extend_from_slice(&[0, 1, 2, 3]);

examples/bag_of_things.rs

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
use orx_imp_vec::*;
2+
3+
#[derive(Default)]
4+
struct Bag {
5+
things: ImpVec<Thing>,
6+
}
7+
8+
impl Bag {
9+
fn push_thing(&self, name: &str) -> ThingInBag {
10+
self.things.imp_push(Thing {
11+
name: name.to_string(),
12+
});
13+
ThingInBag {
14+
bag: self,
15+
thing: &self.things[self.things.len() - 1],
16+
}
17+
}
18+
19+
fn things(&self) -> Vec<&str> {
20+
self.things.iter().map(|x| x.name.as_str()).collect()
21+
}
22+
}
23+
24+
struct Thing {
25+
name: String,
26+
}
27+
28+
#[derive(Clone, Copy)]
29+
struct ThingInBag<'a> {
30+
thing: &'a Thing,
31+
bag: &'a Bag,
32+
}
33+
impl<'a> ThingInBag<'a> {
34+
fn push_thing_in_same_bag(&self, name: &str) -> ThingInBag {
35+
self.bag.push_thing(name)
36+
}
37+
38+
fn is_in_bag(&self, bag: &Bag) -> bool {
39+
let self_bag = self.bag as *const Bag;
40+
let other_bag = bag as *const Bag;
41+
self_bag == other_bag
42+
}
43+
}
44+
impl<'a> PartialEq for ThingInBag<'a> {
45+
fn eq(&self, other: &Self) -> bool {
46+
let same_bag = self.bag as *const Bag == other.bag as *const Bag;
47+
let same_thing = self.thing as *const Thing == other.thing as *const Thing;
48+
same_bag && same_thing
49+
}
50+
}
51+
52+
fn main() {
53+
// create a bag
54+
let bag = Bag::default();
55+
56+
// add things and collect things in bag
57+
let pen = bag.push_thing("pen");
58+
let cup = bag.push_thing("cup");
59+
assert_eq!(bag.things(), ["pen", "cup"]);
60+
61+
// add new things to bag using existing things in bag
62+
pen.push_thing_in_same_bag("pencil");
63+
cup.push_thing_in_same_bag("cupcake");
64+
assert_eq!(bag.things(), ["pen", "cup", "pencil", "cupcake"]);
65+
66+
// create another bag
67+
let other_bag = Bag::default();
68+
let key = other_bag.push_thing("key");
69+
let other_pen = other_bag.push_thing("pen");
70+
71+
// check if things belong to the same bag in constant time
72+
assert_eq!(pen.is_in_bag(&bag), true);
73+
assert_eq!(pen.is_in_bag(&other_bag), false);
74+
75+
assert_eq!(key.is_in_bag(&bag), false);
76+
assert_eq!(key.is_in_bag(&other_bag), true);
77+
78+
// use referential equality to compare if two things are the same
79+
assert!(pen != other_pen);
80+
}

0 commit comments

Comments
 (0)