Repository navigation
Add stack wrapper #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
Siddhi-sahu
wants to merge
12
commits into
rustfoundation:main
Choose a base branch
from
Siddhi-sahu:stack
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
a1eee41
Add std::stack overload test with default constructor
Siddhi-sahu cb9f1bd
Add push, size, top_const helpers (non overloading)
Siddhi-sahu 1d78231
Merge remote-tracking branch 'upstream/main' into stack
Siddhi-sahu 7a3fa38
add copy and move constructors overloads
Siddhi-sahu 64a4d92
Merge branch 'main' into stack
Siddhi-sahu d61bfa1
Merge branch 'main' into stack
Siddhi-sahu dace66b
Add slice, iterator constructors, make copy/move call constructors di…
Siddhi-sahu cdd714e
Add Limitations and Ergonomics
Siddhi-sahu f0738f3
Add failing stack example and overloading analysis
Siddhi-sahu b00213f
Merge branch 'main' into stack
Siddhi-sahu 42dfc6e
add top workaround, skip push overloasds
Siddhi-sahu 47312e1
comment why move_from/copy_from picks the move/copy constructor
Siddhi-sahu File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| //! Test C++ [std::stack][stack] overloads that can't be represented in Rust, and record the macro diagnostics. | ||
| //! Working overloads are in [stack.rs]. | ||
| //! | ||
| //! [stack]: https://en.cppreference.com/cpp/container/stack | ||
| //! [stack.rs]: https://github.com/rustfoundation/overloading-macros/blob/main/cpp-overload-test/src/stdcpp/stack.rs | ||
| //! | ||
| //! The `cpp!` macro doesn't work here. | ||
|
|
||
| #![feature(splat, tuple_trait, c_size_t)] | ||
| #![allow(incomplete_features)] | ||
| #![allow(unused_braces)] | ||
|
|
||
| use splat_overload::overload; | ||
| use std::ffi::c_void; | ||
| use std::marker::PhantomData; | ||
|
|
||
| #[expect(dead_code, reason = "Incompatible overloads can't be used")] | ||
| struct StdStack(*mut c_void, PhantomData<T>); | ||
|
|
||
| type T = std::ffi::c_int; | ||
|
|
||
| overload! { | ||
| impl StdStack { | ||
| /// ### Incompatibilities | ||
| /// | ||
| /// cpp has two `two` top functions with the same name, `T& top()` return an item you can change, | ||
| /// and `const T& top() const` return a read only one. they only differ by const, | ||
| /// which in Rust is `&mut self` reciever vs a `&self ` receiver. | ||
| /// | ||
| /// the overload! cant express this. it turns all overloads with the same name into one | ||
| /// real function, that sends each call to the right version. and one function can | ||
| /// only have one kind of receiver , never `&self` and `&mut self` at the same time. | ||
| /// | ||
| /// ### Workarounds | ||
| /// | ||
| /// The simplest fix would be to not overload at all and use the two names | ||
| /// `top()` for read only version and `top_mut()` for the changeable one. | ||
| /// Different names means theres no overloading is needed , so macro isn't | ||
| /// involved. It will not be same as C++, where both functions are | ||
| /// called top. But its how Rust normally does it, Vec has first() and first_mut() | ||
| /// ANd Rust users would find it natural. | ||
| /// | ||
| /// The other option is like in the `forward_list::front` , take the stack as a normal | ||
| /// argument and not as self, like `top(this: &'static StdStack)` and | ||
| /// `top(this: &'static mut StdStack)` . but the thing is the macro, doesnt support | ||
| /// lifetimes , so the reference has to be static, and the stack has to be leaked , | ||
| /// which is very awkward to use and the callers write `StdStack::top(&stack)` | ||
| /// and not `stack.top()` | ||
| fn top(&self) where T:Sized { | ||
| todo!() | ||
| } | ||
|
|
||
| fn top(&mut self) where T: Sized { | ||
| todo!() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| pub fn main() {} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| error: proc macro panicked | ||
| --> cpp-overload-test/examples/stdcpp-stack.rs:22:1 | ||
| | | ||
| 22 | / overload! { | ||
| 23 | | impl StdStack { | ||
| 24 | | /// ### Incompatibilities | ||
| 25 | | /// | ||
| ... | | ||
| 57 | | } | ||
| | |_^ | ||
| | | ||
| = help: message: all overloads must have the same receiver kind (&self, &mut self, self, or no receiver), got top: &self and top: &mut self | ||
|
|
||
| error: could not compile `cpp-overload-test` (example "stdcpp-stack") due to 1 previous error |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,291 @@ | ||
| //! Test that C++ overloads for [std::stack][stack] can be successfully represented in Rust. | ||
| //! | ||
| //! `std::stack` is a container adapter: it wraps a container (`std::deque<int>` by default ) | ||
| //! [stack]: https://en.cppreference.com/cpp/container/stack | ||
|
|
||
| #![allow(unused_braces, reason = "Generated by overload! macro")] | ||
|
|
||
| use cpp::cpp; | ||
| use splat_overload::overload; | ||
| use std::ffi::{c_char, c_size_t, c_void}; | ||
| use std::marker::PhantomData; | ||
|
|
||
| // C++ header includes | ||
| cpp! {{ | ||
| #include <stack> | ||
| #include <cstddef> | ||
| #include <utility> | ||
| #include <vector> | ||
| #include <ranges> | ||
| }} | ||
|
|
||
| pub type T = std::ffi::c_int; | ||
|
|
||
| /// A wrapper struct named StdStack that holds the returned c++ pointer. | ||
| #[cfg_attr(not(test), expect(dead_code, reason = "only used in tests"))] | ||
| pub struct StdStack(*mut c_void, PhantomData<T>); | ||
|
|
||
| #[cfg_attr(not(test), expect(dead_code, reason = "only used in tests"))] | ||
| #[allow( | ||
| non_upper_case_globals, | ||
| reason = "Triggered by cpp! macro in rust-analyzer" | ||
| )] | ||
| impl StdStack { | ||
| fn default() -> StdStack { | ||
| let stack = unsafe { | ||
| cpp!([] -> *mut c_void as "std::stack<int>*"{ | ||
| return new std::stack<int>(); | ||
| }) | ||
| }; | ||
| StdStack(stack, PhantomData) | ||
| } | ||
|
|
||
| // ### Limitations | ||
| // | ||
| // We want `c_bool` here, but rust only has `c_char` , so `is_empty` converts the | ||
| // `char` back to bool . char is FFI safe, but rust bool is not | ||
| fn is_empty(&self) -> bool { | ||
| let slf = self.0 as *const c_void; | ||
| let empty = unsafe { | ||
| cpp!([slf as "const std::stack<int>*"] -> c_char as "char" { | ||
| return slf->empty(); | ||
| }) | ||
| }; | ||
| empty != 0 | ||
| } | ||
|
|
||
| // ### Limitations | ||
| // | ||
| // c++ `push` has two overloads; `push(const T&)` copies the values in and | ||
| // `push(T&&)` moves it in. They only differ by `&` and `&&`. | ||
| // | ||
| // Only one is wrapped here, as a helper outside `overload!` . | ||
| // The `&`/`&&` difference is covered by copy and move constructors (`new(&StdStack)` and | ||
| // `new(&mut StdStack)`). | ||
| fn push(&mut self, value: T) { | ||
| let slf: *mut c_void = self.0; | ||
| unsafe { | ||
| cpp!([slf as "std::stack<int>*", value as "int"]{ | ||
| slf->push(value); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn size(&self) -> c_size_t { | ||
| let slf = self.0 as *const c_void; | ||
|
|
||
| unsafe { | ||
| cpp!([slf as "const std::stack<int>*"] -> c_size_t as "size_t"{ | ||
| return slf->size(); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn top_const(&self) -> Option<&T> { | ||
| let slf = self.0 as *const c_void; | ||
|
|
||
| unsafe { | ||
| cpp!([slf as "const std::stack<int>*"] -> Option<&T> as "const int*"{ | ||
| //calling top() on an empty stack is undefined behaviour in cpp | ||
| if(slf->empty()){ | ||
| return nullptr; | ||
| }else{ | ||
| return &slf->top(); | ||
| } | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| // *other is const lvalue (from &StdStack) , so only the copy constructor | ||
| // stack(const stack&) matched, The move constructor cant take a | ||
| // const object, so moving isnot possible here. | ||
| fn copy_from(other: &StdStack) -> StdStack { | ||
| let other = other.0 as *const c_void; | ||
| let stack = unsafe { | ||
| cpp!([other as "const std::stack<int>*"] -> *mut c_void as "std::stack<int>*" { | ||
| return new std::stack<int>(*other); | ||
| }) | ||
| }; | ||
| StdStack(stack, PhantomData) | ||
| } | ||
|
|
||
| // std::move(*other) marks the stack as an rvalue | ||
| // so overload picks the move constructor stack (stack&&) instead | ||
| // of copy constructor | ||
| // Do it in one steps: a named variable will be lvalue again and get | ||
| // copied, other is non-const , which is what make the move possible | ||
| fn move_from(other: &mut StdStack) -> StdStack { | ||
| let other = other.0; | ||
| let stack = unsafe { | ||
| cpp!([other as "std::stack<int>*"] -> *mut c_void as "std::stack<int>*" { | ||
| return new std::stack<int>(std::move(*other)); | ||
| }) | ||
| }; | ||
| StdStack(stack, PhantomData) | ||
| } | ||
|
|
||
| fn from_slice(items: &[T]) -> StdStack { | ||
| if items.is_empty() { | ||
| return StdStack::default(); | ||
| } | ||
| let len = items.len(); | ||
|
|
||
| let items: *const T = items.as_ptr(); | ||
| let stack = unsafe { | ||
| cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::stack<int>*" { | ||
| return new std::stack<int>(items, items + len); | ||
| }) | ||
| }; | ||
| StdStack(stack, PhantomData) | ||
| } | ||
|
|
||
| fn from_iter(iter: &mut dyn Iterator<Item = T>) -> StdStack { | ||
| let items: Vec<T> = iter.collect(); | ||
| // An empty Rust Vec is guaranteed to have a dangling pointer, see | ||
| // <https://doc.rust-lang.org/std/vec/struct.Vec.html#method.as_ptr> | ||
| if items.is_empty() { | ||
| return StdStack::default(); | ||
| } | ||
| let len = items.len(); | ||
| let items: *const T = items.as_ptr(); | ||
| // SAFETY: items is a non-null pointer to len properly initialized values of T | ||
| let stack = unsafe { | ||
| cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::stack<int>*"{ | ||
| std::vector<int> vec = std::vector<int>(items, items + len); | ||
| #ifdef __cpp_lib_containers_ranges | ||
| return new std::stack<int>(std::from_range, vec); | ||
| #else | ||
| #error "std::from_range is not available, try using `-std=c++23` or a newer compiler" | ||
| #endif | ||
| }) | ||
| }; | ||
| StdStack(stack, PhantomData) | ||
| } | ||
| } | ||
|
|
||
| // Constructors: `new(...)` | ||
| // | ||
| // ### Limitations | ||
| // | ||
| // These contructors are not wrapped yet: | ||
| // - `stack(const Container&)` and `stack(Container&&)`: they take a cpp `std::deque<int>`, | ||
| // so they need a rust wrapper for `deque` first, which is not done yet. | ||
| // - constructors that differ by allocator argument : ignored, because they are not interesting overloads | ||
| // to cover. | ||
| overload! { | ||
| impl StdStack { | ||
| /// an empty stack | ||
| pub fn new() -> StdStack { | ||
| StdStack::default() | ||
| } | ||
| /// Clone stack items into a new stack from a shared stack reference | ||
| /// | ||
| /// ### Ergonomics | ||
| /// | ||
| /// `new(&s)` copies and `new(&mut s)` moves, but at the call site they look almost | ||
| /// same and do very different things, | ||
| /// it will be very easy to mix them up. | ||
| pub fn new(other: &StdStack) -> StdStack { | ||
| StdStack::copy_from(other) | ||
| } | ||
|
|
||
| /// Move stack items into a new stack from a mutable stack reference. | ||
| /// | ||
| /// ### Limitations | ||
| /// | ||
| /// After a cpp move, the old stack still exists in a "valid but unspecified" state, | ||
| /// which is empty in practice. A Rust moves makes the old variable unusauable , | ||
| /// so idiomatic rust would take the stack by value instead of `&mut` | ||
| pub fn new(other: &mut StdStack) -> StdStack { | ||
| StdStack::move_from(other) | ||
| } | ||
|
|
||
| /// constructs a stack by copying items from a slice. | ||
| pub fn new(items: &[T]) -> StdStack { | ||
| StdStack::from_slice(items) | ||
| } | ||
|
|
||
| /// constructs a stack from an iterator | ||
| /// | ||
| /// ### Limitations | ||
| /// The caller has to cast the iterator, like `&mut iter as &mut dyn Iterator<Item = _>` | ||
| /// because rust doesnot automatically convert it to `dyn Iterator` automatically | ||
| /// | ||
| /// It used the dyn Iterator and not a generic `impl Iterator` because overload! | ||
| /// doesn't support generics in this position yet and a `impl Iterator` | ||
| /// could overlap with the `&[T]` overload. | ||
| /// | ||
| /// the items are basically copied twice, first into a Rust `Vec` and | ||
| /// then into a cpp `std::vector`. | ||
| /// its simple, but it do the extra work. | ||
| /// In c++, we need a `std::from_range` tag to tell this constructor apart from the others. | ||
| /// Rust doesnot need a tag because argu,ment types are already there. | ||
| pub fn new(iter: &mut dyn Iterator<Item = T>) -> StdStack { | ||
| StdStack::from_iter(iter) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// These tests show that the correct Rust and C++ overloads are being called. | ||
| #[test] | ||
| pub fn test_stack() { | ||
| let mut default_stack = StdStack::new(); | ||
| assert!(default_stack.is_empty()); | ||
|
|
||
| //push, size, top_const | ||
| let mut stack = StdStack::new(); | ||
| assert_eq!(stack.size(), 0); | ||
| assert_eq!(stack.top_const(), None); | ||
|
|
||
| stack.push(10); | ||
| stack.push(20); | ||
| assert!(!stack.is_empty()); | ||
| assert_eq!(stack.size(), 2); | ||
| assert_eq!(stack.top_const(), Some(&20)); | ||
|
|
||
| //copy constructor | ||
| let mut original = StdStack::new(); | ||
| original.push(13); | ||
| original.push(09); | ||
|
|
||
| let copy = StdStack::new(&original); | ||
| assert_eq!(copy.size(), 2); | ||
| assert_eq!(copy.top_const(), Some(&09)); | ||
|
|
||
| //changing original will not change copy, because copy is independent | ||
| original.push(10); | ||
| assert_eq!(copy.size(), 2); | ||
| assert_eq!(original.size(), 3); | ||
|
|
||
| // move constructor | ||
| let mut real = StdStack::new(); | ||
| real.push(10); | ||
| real.push(20); | ||
| real.push(30); | ||
|
|
||
| let moved_stack = StdStack::new(&mut real); | ||
|
teor2345 marked this conversation as resolved.
Resolved
|
||
| assert_eq!(moved_stack.size(), 3); | ||
| assert_eq!(moved_stack.top_const(), Some(&30)); | ||
| assert!(real.is_empty()); | ||
| assert_eq!(real.size(), 0); | ||
| assert_eq!(real.top_const(), None); | ||
| //moving an empty stack is fine too | ||
| let moved_empty = StdStack::new(&mut default_stack); | ||
| assert!(moved_empty.is_empty()); | ||
|
|
||
| // ### Limitations: with overload!, caller has to call .as_slice(), because array reference doesnot convert into slice automatically. | ||
| // no mut since, from_slice is never changed. | ||
| let from_slice = StdStack::new([1, 2, 3].as_slice()); | ||
| assert_eq!(from_slice.size(), 3); | ||
| assert_eq!(from_slice.top_const(), Some(&3)); | ||
| let from_empty_slice = StdStack::new([].as_slice()); | ||
| assert!(from_empty_slice.is_empty()); | ||
|
|
||
| let mut iter: std::array::IntoIter<T, 3> = [1, 2, 3].into_iter(); | ||
| let from_iter = StdStack::new(&mut iter as &mut dyn Iterator<Item = _>); | ||
| assert_eq!(from_iter.size(), 3); | ||
| assert_eq!(from_iter.top_const(), Some(&3)); | ||
| let mut empty_iter: std::array::IntoIter<T, 0> = [].into_iter(); | ||
| let from_empty_iter = StdStack::new(&mut empty_iter as &mut dyn Iterator<Item = _>); | ||
| assert!(from_empty_iter.is_empty()); | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.