Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 25 additions & 4 deletions .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ jobs:
test-rust:
name: 2. Test Rust crates
runs-on: ubuntu-latest
needs:
- build-rust
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
Expand All @@ -44,15 +46,17 @@ jobs:
cargo test --doc --all-features

run:
name: 3. Run Rust example binaries
name: 3. Run Rust overload-test binaries
runs-on: ubuntu-latest
needs:
- build-rust
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
with:
toolchain: nightly
components: cargo,clippy,rustfmt
- name: Run Rust example binaries
- name: Run Rust overload-test binaries
# Find all test crate binaries, extract the basename (no directories or extensions),
# then run them one at a time with cargo.
# There is no default-run binary set in Cargo.toml.
Expand Down Expand Up @@ -100,6 +104,8 @@ jobs:
publish-dry-run:
name: 7. Dry run publishing
runs-on: ubuntu-latest
needs:
- build-rust
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
Expand All @@ -112,6 +118,8 @@ jobs:
diagnostics-rust:
name: 8. Diagnostics tests on Rust crates
runs-on: ubuntu-latest
needs:
- build-rust
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
Expand Down Expand Up @@ -150,7 +158,10 @@ jobs:

cpp-overload-test-ok:
name: 9. Check C++ overloads
runs-on: ubuntu-latest
# Ubuntu 26.04 is needed for C++23 standard library features.
runs-on: ubuntu-26.04
needs:
- build-rust
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
Expand All @@ -161,6 +172,12 @@ jobs:
# FIXME: turn this into a script so contributors can run it locally.
- name: Check C++ overload ok cases
run: |
echo "Checking the crate library builds on a newer platform:"
cargo build --all-targets --all-features
cargo clippy --all-targets --all-features -- --deny warnings
cargo test --all-targets --all-features
cargo test --doc --all-features
echo "Checking the C++ overload test crate builds:"
cargo build --all-features --package cpp-overload-test --lib
cargo clippy --all-features --package cpp-overload-test -- --deny warnings
cargo test --all-features --package cpp-overload-test --lib
Expand All @@ -169,7 +186,10 @@ jobs:

cpp-overload-test-fail:
name: 10. Must-fail C++ overloads
runs-on: ubuntu-latest
runs-on: ubuntu-26.04
# Don't run unless the test crate has built successfully, crates arealways built for examples.
needs:
- cpp-overload-test-ok
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1.17.0
Expand All @@ -182,6 +202,7 @@ jobs:
# and disable color output.
# FIXME: turn this into a script so contributors can run it locally.
run: |
echo "Checking must-fail example builds:"
export CARGO_TERM_COLOR=never
set -o pipefail
TEST_NAMES=$(find cpp-overload-test/examples -name "*.rs" | \
Expand Down
7 changes: 6 additions & 1 deletion cpp-overload-test/build.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
fn main() {
// `cpp_build` doesn't work with multiple example/binary builds in the same crate, so we use
// lib.rs submodules for successful overloads.
cpp_build::build("src/lib.rs");
cpp_build::Config::new()
// Required for range overloads.
.flag("-std=c++23")
// Ignore `warning: 'std::rel_ops' is deprecated: use '<=>' instead [-Wdeprecated-declarations]`
.flag("-Wno-deprecated-declarations")
.build("src/lib.rs");
}
150 changes: 131 additions & 19 deletions cpp-overload-test/src/stdcpp/forward_list.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,16 @@ use std::ffi::{c_int, c_void};
// C++ header includes
cpp! {{
#include <forward_list>
#include <vector>
#include <ranges>
}}

/// A wrapper struct to hold the returned C++ pointer.
///
/// ### Limitations
///
/// This struct and the impl should be fully generic over the list type.
/// FIXME: try this and see if the `overload!` macro and `splat` feature can handle it.
struct StdForwardList(*mut c_void);

// The cpp! macro doesn't work inside the overload! macro, so we extract C++ calls into separate methods.
Expand All @@ -25,19 +32,64 @@ impl StdForwardList {
StdForwardList(list)
}

fn with_capacity(capacity: usize) -> StdForwardList {
fn repeat_default(count: usize) -> StdForwardList {
let list = unsafe {
cpp!([capacity as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
return new std::forward_list<int>(capacity);
cpp!([count as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
return new std::forward_list<int>(count);
})
};
StdForwardList(list)
}

fn repeat_with(value: c_int, capacity: usize) -> StdForwardList {
fn repeat_with(value: c_int, count: usize) -> StdForwardList {
let list = unsafe {
cpp!([value as "int", capacity as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
return new std::forward_list<int>(value, capacity);
cpp!([value as "int", count as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
return new std::forward_list<int>(value, count);
})
};
StdForwardList(list)
}

/// ### Limitations
///
/// The slice satisfies C++ `ContiguousIterator`, even though it's not a requirement for this
/// C++ iterator overload.
/// FIXME: allow non-contiguous iterators, if that makes sense in Rust.
fn from_slice(items: &[c_int]) -> StdForwardList {
let len = items.len();
let items: *const c_int = items.as_ptr();
let list = unsafe {
cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
// In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`.
return new std::forward_list<int>(items, items + len);
})
};
StdForwardList(list)
}

/// ### Limitations
///
/// `std::from_range` is used as a tag to disambiguate this constructor, it is part of the C++
/// standard library API. The Rust overload requires a cast to `dyn Iterator` instead, but this
/// is likely just a macro limitation.
///
/// This implementation builds a Rust `Vec` and C++ `std::vector` from the iterator for simplicity.
/// A production implementation could use a Rust-to-C++ iterator-to-range adapter.
fn from_iter(iter: &mut dyn Iterator<Item = c_int>) -> StdForwardList {
let items: Vec<c_int> = iter.collect();
let len = items.len();
let items: *const c_int = items.as_ptr();
let list = unsafe {
cpp!([items as "const int*", len as "size_t"] -> *mut c_void as "std::forward_list<int>*" {
// Use the legacy iterator constructor for `vector`.
// In C++, `items + len` increments the pointer by `len` elements of `sizeof(int)`.
std::vector<int> vec = std::vector<int>(items, items + len);
// `vector` is a range, so we can use it to construct a `forward_list`.
#ifdef __cpp_lib_containers_ranges
return new std::forward_list<int>(std::from_range, vec);
#else
#error "std::from_range is not available, try using `-std=c++23` or a newer compiler"
#endif
})
};
StdForwardList(list)
Expand All @@ -64,6 +116,18 @@ impl StdForwardList {
};
StdForwardList(list)
}

/// ### Limitations
///
/// This is just an example, a production implementation would support any number of arguments.
fn from_initializer_list(a: c_int, b: c_int, c: c_int) -> StdForwardList {
let list = unsafe {
cpp!([a as "int", b as "int", c as "int"] -> *mut c_void as "std::forward_list<int>*" {
return new std::forward_list<int>{ a, b, c };
})
};
StdForwardList(list)
}
}

// We ignore constructors that only differ by an allocator argument, because they're not
Expand All @@ -75,37 +139,85 @@ overload! {
StdForwardList::default()
}

/// Construct a list with a given capacity.
fn new(capacity: usize) -> StdForwardList {
StdForwardList::with_capacity(capacity)
/// Construct a list with `count` default-constructed items.
fn new(count: usize) -> StdForwardList {
StdForwardList::repeat_default(count)
}

/// Construct a list filled with `count` instances of the given value.
fn new(value: c_int, count: usize) -> StdForwardList {
StdForwardList::repeat_with(value, count)
}

/// Construct a list filled with the given value.
fn new(value: c_int, capacity: usize) -> StdForwardList {
StdForwardList::repeat_with(value, capacity)
/// Construct a list by copying items from a slice.
fn new(items: &[c_int]) -> StdForwardList {
StdForwardList::from_slice(items)
}

/// Clone a list from a shared reference.
/// Construct a list from an iterator.
///
/// ### Limitations
///
/// `&mut dyn Iterator<T>` (and `&mut Iterator<T>`) can never be the same type as `&[T]`,
/// but passing an owned `impl Iterator<T>` here could overlap with the `&[T]` overload.
///
/// The `overload!` macro doesn't support generics in this position yet, so we use
/// `dyn Trait` instead.
/// FIXME: make the macro support generic iterators, or handle `impl Iterator` correctly.
fn new(iter: &mut dyn Iterator<Item = c_int>) -> StdForwardList {
StdForwardList::from_iter(iter)
}

/// Clone list items into a new list from a shared list reference.
fn new(other: &StdForwardList) -> StdForwardList {
StdForwardList::copy_from(other)
}

/// Move list items from a mutable reference into a new list.
/// Move list items into a new list from a mutable list reference.
fn new(other: &mut StdForwardList) -> StdForwardList {
StdForwardList::move_from(other)
}

// TODO:
// iterator
// range
// initializer_list
/// Construct a list from the supplied items.
///
/// ### Limitations
///
/// This is just an example, a production implementation would support any number of arguments.
/// Rust doesn't have a language equivalent to C++'s `initializer_list`, and it doesn't
/// have variadic tuples, so we can't overload on the tuple trait itself.
///
/// The following initializer argument counts clash with other overloads:
/// - 0: the default no-argument constructor
/// - 1: the `count` default-value repetition constructor, if `T` is `size_t`
/// - most constructors take 1 argument, so there could be other clashes in unusual
/// circumstances.
/// - 2: the `value` repetition constructor, if `T` is `size_t`
fn new(a: c_int, b: c_int, c: c_int) -> StdForwardList {
StdForwardList::from_initializer_list(a, b, c)
}
}
}

pub fn test_forward_list() {
let mut default_list = StdForwardList::new();
let _with_capacity = StdForwardList::new(10);
let _repeat_default = StdForwardList::new(10);
let _repeat_with = StdForwardList::new(42, 100);

// ### Limitations
//
// The `as_slice` call is required to match the overload, an array doesn't automatically coerce.
// FIXME: maybe add a const generic overload for arrays.
let _from_slice = StdForwardList::new([1, 2, 3].as_slice());

// ### Limitations
//
// The cast is required to match the overload, an iterator doesn't automatically coerce.
// It would be more ergonomic for users to collect the iterator themselves, then use the slice
// overload, or create a Rust/C++ iterator-to-range adapter.
let mut iter = [1, 2, 3].iter().copied();
let _from_iter = StdForwardList::new(&mut iter as &mut dyn Iterator<Item = _>);

let _ref_clone = StdForwardList::new(&default_list);
let _mut_clone = StdForwardList::new(&mut default_list);
let _from_initializer_list = StdForwardList::new(1, 2, 3);
}
Loading