When using VecDeque
and BTreeMap
in std::collections
, I frequently feel the need of an unsafe
version of the APIs that return Option
or Result
. For example,
fn main() {
let mut deque = std::collections::VecDeque::new();
deque.push_back(1usize);
match deque.pop_front() {
None=>unreachable!(),
Some(_)=>todo!(),
}
}
For me, this code works, but 1) has performance compromise when checking whether the popped value is None
at runtime (maybe it got optimized away in this toy example but in general case this is not decidable) and 2) there is something aesthetically unsatisfying about having to write the runtime check when I would rather hand a proof to compiler to tell it that deque
can not be empty, or, as a second choice, I could at least assume it in the compiler.
I am not sure this would be a misuse of unsafe
, but I am thinking about an api of VecDeque
pub unsafe fn pop_front_unsafe(&mut self)->T;
// as a comparison, the pop_front method's signature is as follows:
// pub fn pop_front(&mut self)->Option<T>{}
which is unsafe
because the behaviour is undefined when the VecDeque
is empty.
With this API, we can write
fn main() {
let mut deque = std::collections::VecDeque::new();
let number = unsafe {
deque.push_back(1usize);
deque.pop_front_unsafe()
};
todo!()
}
This has some advantages:
- The assumption is explicitly made, rather than through an indirection stating that if it returns an
Option
theOption
is exactly of variantSome
not `None and - No runtime check.
- A clear distinction between maintaining the consistent state of the data structure and its permissible operations (among which
unsafe
belongs to latter, if I am usingunsafe
correctly). This makes the data structure more extensible. For example, we could have :
pub fn push_and_pop_if_full<T>(&mut self,t:T)->Option<T> {
unsafe {
let capacity = self.capacity();
self.push_back(t);
if self.len() > capacity {
Some(self.pop_front_unsafe())
} else {
None
}
}
}
As far as I know, the above would not be possible without pop_front_unsafe
for a user that does not define struct VecDeque
.
Some possible problems I see in doing this:
- It is not what
unsafe
is supposed to do, in which case, I can agree to use other syntactical construct/keyword. - This will make the users of the data structure too powerful and give them too much flexibility.
- The APIs would still be too restrictive for many cases anyway, and ultimately we need the
VecDeque
owner to implement the actual best AND safe APIs for us. For example, we might consecutively insertn
elements and then pop the front if needed until its capacity is not exceeded. To efficiently implement this might require touching thebuf
private field directly.
To make it short, it seems to me that it might be better if we have two (or more) levels of APIs, where at some higher levels the consistent state is maintained with a set of safe and well-defined operations and at lower levels the more fine-grained control which allows for composition of more complex operations that is also safe but more efficient, whose enumeration is a mathematically undecidable problem but whose individual cases could be relatively easy.
What do you think?