Repository navigation
Add unordered_multiset wrapper #51
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
Lynndabel
wants to merge
5
commits into
rustfoundation:main
Choose a base branch
from
Lynndabel:unordered_multiset
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.
+213
−0
Open
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
c3f44a0
added unordered_multisetmodule
Lynndabel 371f659
Add unordered_multiset wrapper
Lynndabel b0c7534
Merge branch 'main' into unordered_multiset
Lynndabel 087664b
Merge branch 'main' into unordered_multiset
Lynndabel be53e07
Split documentation and add missing constructor overloads
Lynndabel 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
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,212 @@ | ||
| //! Test that C++ overloads for [std::unordered_multiset][unordered_multiset] can be successfully represented in Rust. | ||
| //! | ||
| //! [unordered_multiset]: https://en.cppreference.com/cpp/container/unordered_multiset | ||
|
|
||
| #![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 <unordered_set> | ||
| #include <vector> | ||
| #include <cstddef> | ||
| }} | ||
|
|
||
| /// A wrapper struct to hold the returned C++ `std::unordered_multiset<int>` pointer. | ||
| /// | ||
| /// ### Limitations | ||
| /// | ||
| /// This struct and the impl should be fully generic over the set type. | ||
| /// FIXME: make the `overload!` macro support generics. | ||
| #[cfg_attr(not(test), expect(dead_code, reason = "Only used in tests"))] | ||
| pub struct StdUnorderedMultiset(*mut c_void, PhantomData<T>); | ||
|
|
||
| /// Workaround for missing generic support in the `overload!` macro. | ||
| pub type T = std::ffi::c_int; | ||
|
|
||
| #[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 StdUnorderedMultiset { | ||
| fn default() -> StdUnorderedMultiset { | ||
| let set = unsafe { | ||
| cpp!([] -> *mut c_void as "std::unordered_multiset<int>*" { | ||
| return new std::unordered_multiset<int>(); | ||
| }) | ||
| }; | ||
| StdUnorderedMultiset(set, PhantomData) | ||
| } | ||
|
|
||
| fn from_bucket_count(bucket_count: c_size_t) -> StdUnorderedMultiset { | ||
| let set = unsafe { | ||
| cpp!([bucket_count as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" { | ||
| return new std::unordered_multiset<int>(bucket_count); | ||
| }) | ||
| }; | ||
| StdUnorderedMultiset(set, PhantomData) | ||
| } | ||
|
|
||
| fn copy_from(other: &StdUnorderedMultiset) -> StdUnorderedMultiset { | ||
| let other_ptr = other.0 as *const c_void; | ||
| let set = unsafe { | ||
| cpp!([other_ptr as "const std::unordered_multiset<int>*"] -> *mut c_void as "std::unordered_multiset<int>*" { | ||
| return new std::unordered_multiset<int>(*other_ptr); | ||
| }) | ||
| }; | ||
| StdUnorderedMultiset(set, PhantomData) | ||
| } | ||
|
|
||
| fn from_slice(items: &[T]) -> StdUnorderedMultiset { | ||
| let len = items.len(); | ||
| let items: *const T = items.as_ptr(); | ||
| let set = unsafe { | ||
| cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::unordered_multiset<int>*" { | ||
| return new std::unordered_multiset<int>(items, items + len); | ||
| }) | ||
| }; | ||
| StdUnorderedMultiset(set, PhantomData) | ||
| } | ||
|
|
||
| fn from_initializer_list_2(a: T, b: T) -> StdUnorderedMultiset { | ||
| let set = unsafe { | ||
| cpp!([a as "int", b as "int"] -> *mut c_void as "std::unordered_multiset<int>*" { | ||
| return new std::unordered_multiset<int>{ a, b }; | ||
| }) | ||
| }; | ||
| StdUnorderedMultiset(set, PhantomData) | ||
| } | ||
|
|
||
| fn insert_value(&mut self, value: T) { | ||
| let slf: *mut c_void = self.0; | ||
| unsafe { | ||
| cpp!([slf as "std::unordered_multiset<int>*", value as "int"] { | ||
| slf->insert(value); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn insert_range(&mut self, items: &[T]) { | ||
| let slf: *mut c_void = self.0; | ||
| let len = items.len(); | ||
| let items: *const T = items.as_ptr(); | ||
| unsafe { | ||
| cpp!([slf as "std::unordered_multiset<int>*", items as "const int*", len as "size_t"] { | ||
| slf->insert(items, items + len); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn count(&self, value: T) -> c_size_t { | ||
| let slf = self.0 as *const c_void; | ||
| unsafe { | ||
| cpp!([slf as "const std::unordered_multiset<int>*", value as "int"] -> c_size_t as "size_t" { | ||
| return slf->count(value); | ||
| }) | ||
| } | ||
| } | ||
|
|
||
| fn is_empty(&self) -> bool { | ||
| let slf = self.0 as *const c_void; | ||
| let empty = unsafe { | ||
| cpp!([slf as "const std::unordered_multiset<int>*"] -> c_char as "char" { | ||
| return slf->empty(); | ||
| }) | ||
| }; | ||
| empty != 0 | ||
| } | ||
| } | ||
|
|
||
| overload! { | ||
| impl StdUnorderedMultiset { | ||
| /// Construct an empty set. | ||
| /// | ||
| /// ### Limitations | ||
| /// | ||
| /// The `overload!` macro does not support generics, so the implementation is hardcoded to `int` type. | ||
| /// Rust does not have default function arguments, so C++ constructors with default arguments | ||
| /// (e.g., `unordered_multiset(size_type bucket_count, const Hash& hash = Hash())`) | ||
| /// require separate overloads for each argument combination. | ||
| pub fn new() -> StdUnorderedMultiset { | ||
| StdUnorderedMultiset::default() | ||
| } | ||
|
|
||
| /// Construct a set with at least `bucket_count` buckets. | ||
| pub fn new(bucket_count: c_size_t) -> StdUnorderedMultiset { | ||
| StdUnorderedMultiset::from_bucket_count(bucket_count) | ||
| } | ||
|
|
||
| /// Construct a set by copying from another set. | ||
| pub fn new(other: &StdUnorderedMultiset) -> StdUnorderedMultiset { | ||
| StdUnorderedMultiset::copy_from(other) | ||
| } | ||
|
|
||
| /// Construct a set by copying items from a slice. | ||
| /// | ||
| /// ### Ergonomics | ||
| /// | ||
| /// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce. | ||
| pub fn new(items: &[T]) -> StdUnorderedMultiset { | ||
| StdUnorderedMultiset::from_slice(items) | ||
| } | ||
|
|
||
| /// Construct a set from an initializer list. | ||
| /// | ||
| /// ### Ergonomics | ||
| /// | ||
| /// Initializer list overloads require separate functions for each argument count, which is not ergonomic. | ||
| pub fn new(a: T, b: T) -> StdUnorderedMultiset { | ||
| StdUnorderedMultiset::from_initializer_list_2(a, b) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| overload! { | ||
| impl StdUnorderedMultiset { | ||
| /// Insert a single value into the set. | ||
| pub fn insert(&mut self, value: T) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's a lot of insert overloads as well, feel free to update this PR in stages, and ask for a review after every stage. |
||
| self.insert_value(value) | ||
| } | ||
|
|
||
| /// Insert a range of values from a slice into the set. | ||
| /// | ||
| /// ### Ergonomics | ||
| /// | ||
| /// The `as_slice()` call is required to match the slice overload, as arrays don't automatically coerce. | ||
| pub fn insert(&mut self, items: &[T]) { | ||
| self.insert_range(items) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| #[test] | ||
| pub fn test_unordered_multiset() { | ||
| let default_set = StdUnorderedMultiset::new(); | ||
| assert!(default_set.is_empty()); | ||
|
|
||
| let from_slice = StdUnorderedMultiset::new([1, 2, 3].as_slice()); | ||
| assert_eq!(from_slice.count(1), 1); | ||
|
|
||
| let from_bucket_count = StdUnorderedMultiset::new(10 as c_size_t); | ||
| assert!(from_bucket_count.is_empty()); | ||
|
|
||
| let from_initializer_list = StdUnorderedMultiset::new(2, 1); | ||
| assert_eq!(from_initializer_list.count(2), 1); | ||
|
|
||
| let empty_set = StdUnorderedMultiset::new(); | ||
| let copied = StdUnorderedMultiset::new(&empty_set); | ||
| assert!(copied.is_empty()); | ||
|
|
||
| let mut insert_set = StdUnorderedMultiset::new(); | ||
| insert_set.insert(42); | ||
| assert_eq!(insert_set.count(42), 1); | ||
|
|
||
| let mut insert_set = StdUnorderedMultiset::new(); | ||
| insert_set.insert([1, 2, 3].as_slice()); | ||
| assert_eq!(insert_set.count(1), 1); | ||
| } | ||
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To check that an overload set works in Rust, we need to wrap all of the C++ constructor overloads.
There are 14 constructor overloads in C++, excluding overloads that only differ by an allocator argument:
https://en.cppreference.com/cpp/container/unordered_multiset/unordered_multiset
That's a lot of overloads, so you don't have to implement them all. Instead, pick overloads that are very different from each other. For example, you could add one overload starting with these arguments:
It would also be interesting to use C++ default arguments to call an overload with just one argument of type:
This would be enough to cover the constructor overload set reasonably well.
Please document the lack of default argument values in Rust as a limitation.