diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2928ac5..1a8b967 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -40,9 +40,8 @@ jobs: - uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@master + uses: dtolnay/rust-toolchain@stable with: - toolchain: nightly-2022-08-22 components: rustfmt, clippy - name: Cache cargo registry @@ -60,7 +59,7 @@ jobs: run: cargo fmt --all -- --check - name: Run Clippy - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets --all-features -- -D warnings - name: Check file size limit run: python3 scripts/check_file_size.py @@ -77,9 +76,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2022-08-22 + uses: dtolnay/rust-toolchain@stable - name: Cache cargo registry uses: actions/cache@v4 @@ -105,10 +102,8 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Setup Rust nightly - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2022-08-22 + - name: Setup Rust + uses: dtolnay/rust-toolchain@stable # Install tarpaulin using pre-built binary (avoids compilation issues) - name: Install cargo-tarpaulin @@ -154,9 +149,7 @@ jobs: - uses: actions/checkout@v4 - name: Setup Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2022-08-22 + uses: dtolnay/rust-toolchain@stable - name: Cache cargo registry uses: actions/cache@v4 @@ -224,9 +217,7 @@ jobs: fetch-depth: 0 - name: Setup Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2022-08-22 + uses: dtolnay/rust-toolchain@stable - name: Check if version changed id: version_check @@ -272,9 +263,7 @@ jobs: token: ${{ secrets.GITHUB_TOKEN }} - name: Setup Rust - uses: dtolnay/rust-toolchain@master - with: - toolchain: nightly-2022-08-22 + uses: dtolnay/rust-toolchain@stable - name: Configure git run: | diff --git a/README.md b/README.md index bac34c9..54e01be 100644 --- a/README.md +++ b/README.md @@ -53,12 +53,24 @@ use platform_data::Flow; let mut collected = vec![]; +// Use Flow with try_for_each by converting to ControlFlow (0..20).try_for_each(|i| { collected.push(i); - if i == 10 { Flow::Break } else { Flow::Continue } + if i == 10 { Flow::Break.into_control_flow() } else { Flow::Continue.into_control_flow() } }); assert_eq!(collected, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); + +// Or use Flow directly with manual iteration +let mut collected2 = vec![]; +for i in 0..20 { + collected2.push(i); + let flow = if i == 10 { Flow::Break } else { Flow::Continue }; + if flow.is_break() { + break; + } +} +assert_eq!(collected2, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); ``` ### Using Point for repeated elements @@ -144,17 +156,7 @@ use platform_data::{Links, LinkType, LinksConstants, Flow, Error, ReadHandler, W ## Requirements -This crate requires the **nightly** Rust toolchain due to the use of unstable features: - -- `try_trait_v2` -- `associated_type_bounds` -- `type_alias_impl_trait` -- `const_refs_to_cell` -- `const_result_drop` -- `const_trait_impl` -- `const_convert` -- `const_deref` -- `step_trait` +This crate requires **Rust 1.79 or later** (stable toolchain). The `associated_type_bounds` feature used for `Error:` bounds was stabilized in Rust 1.79. ## Dependencies diff --git a/changelog.d/20251227_183811_migrate_to_stable_rust.md b/changelog.d/20251227_183811_migrate_to_stable_rust.md new file mode 100644 index 0000000..1011b9e --- /dev/null +++ b/changelog.d/20251227_183811_migrate_to_stable_rust.md @@ -0,0 +1,23 @@ +### Changed +- Migrated from nightly Rust to **stable Rust** toolchain (requires Rust 1.79+) +- Removed all unstable feature flags: + - `try_trait_v2` - Flow no longer implements `Try` trait + - `type_alias_impl_trait` - Point now uses explicit `PointIter` type + - `const_trait_impl`, `const_convert`, `const_deref`, `const_refs_to_cell`, `const_result_drop` - LinkType/FuntyPart traits are no longer const + - `step_trait` - LinkType no longer requires `Step` bound + - `associated_type_bounds` - still used but stabilized in Rust 1.79 +- `Flow` type changes: + - Added `into_control_flow()` method for use with `try_for_each` + - Removed `Try` and `FromResidual` trait implementations (nightly-only) +- `Point` type changes: + - Added explicit `PointIter` iterator type (publicly exported) +- `LinkType` trait changes: + - Removed `Step` trait bound + - Removed `const` from trait and impl +- `FuntyPart` trait changes: + - Simplified implementation without const generics + - Now uses `expect()` instead of `unreachable_unchecked()` +- Updated CI/CD pipeline to use `dtolnay/rust-toolchain@stable` + +### Fixed +- Crate now compiles on stable Rust without any feature flags diff --git a/rust-toolchain.toml b/rust-toolchain.toml index d7c5705..292fe49 100644 --- a/rust-toolchain.toml +++ b/rust-toolchain.toml @@ -1,2 +1,2 @@ [toolchain] -channel = "nightly-2022-08-22" +channel = "stable" diff --git a/rustfmt.toml b/rustfmt.toml index 4dd7be3..02a3e70 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,5 +1,3 @@ -error_on_line_overflow = true -error_on_unformatted = true -version = "Two" - -imports_granularity = "Crate" \ No newline at end of file +# Stable rustfmt configuration +# For available options, see: https://rust-lang.github.io/rustfmt/ +edition = "2018" diff --git a/src/constants.rs b/src/constants.rs index 3cb2b4b..597a87b 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -96,7 +96,7 @@ impl LinksConstants { pub fn is_external(&self, address: T) -> bool { self.external_range .clone() - .map_or(false, |range| range.contains(&address)) + .is_some_and(|range| range.contains(&address)) } pub fn is_reference(&self, address: T) -> bool { diff --git a/src/converters.rs b/src/converters.rs index 3cc2800..5e3b966 100644 --- a/src/converters.rs +++ b/src/converters.rs @@ -1,6 +1,4 @@ use crate::{Hybrid, LinkType}; -use funty::Integral; -use std::ops::Sub; #[derive(Default)] pub struct AddrToRaw; diff --git a/src/flow.rs b/src/flow.rs index 70bb581..4c88463 100644 --- a/src/flow.rs +++ b/src/flow.rs @@ -1,29 +1,46 @@ -use std::ops::{ControlFlow, FromResidual, Try}; +use std::ops::ControlFlow; +/// Represents the control flow of an operation, similar to `ControlFlow`. +/// +/// This is a simplified enum that can be used with iterators and callbacks +/// to indicate whether to continue or break early. #[repr(usize)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum Flow { Continue, Break, } -impl FromResidual for Flow { - fn from_residual(_: ::Residual) -> Self { - Flow::Break +impl Flow { + /// Returns `true` if this is `Flow::Continue`. + pub fn is_continue(&self) -> bool { + matches!(self, Flow::Continue) } -} - -impl Try for Flow { - type Output = (); - type Residual = Flow; - fn from_output(_: Self::Output) -> Self { - Flow::Continue + /// Returns `true` if this is `Flow::Break`. + pub fn is_break(&self) -> bool { + matches!(self, Flow::Break) } - fn branch(self) -> ControlFlow { + /// Converts this Flow into a ControlFlow that can be used with try_for_each. + /// + /// This method enables using Flow with iterator methods like `try_for_each`: + /// + /// ``` + /// use platform_data::Flow; + /// use std::ops::ControlFlow; + /// + /// let mut count = 0; + /// let result = (0..10).try_for_each(|i| { + /// count += 1; + /// if i == 5 { Flow::Break.into_control_flow() } else { Flow::Continue.into_control_flow() } + /// }); + /// assert_eq!(count, 6); + /// ``` + pub fn into_control_flow(self) -> ControlFlow<()> { match self { Flow::Continue => ControlFlow::Continue(()), - Flow::Break => ControlFlow::Break(Flow::Break), + Flow::Break => ControlFlow::Break(()), } } } @@ -36,3 +53,12 @@ impl From> for Flow { } } } + +impl From for ControlFlow<()> { + fn from(flow: Flow) -> Self { + match flow { + Flow::Continue => ControlFlow::Continue(()), + Flow::Break => ControlFlow::Break(()), + } + } +} diff --git a/src/hybrid.rs b/src/hybrid.rs index f2368b0..bbcaf2a 100644 --- a/src/hybrid.rs +++ b/src/hybrid.rs @@ -1,6 +1,4 @@ use crate::LinkType; -use funty::Integral; -use std::ops::{Div, Sub}; #[derive(Debug, Clone, Copy, Hash, PartialOrd, PartialEq, Ord, Eq)] pub struct Hybrid { diff --git a/src/lib.rs b/src/lib.rs index ee19c31..fe865e7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,3 @@ -#![feature(try_trait_v2)] -#![feature(associated_type_bounds)] -#![feature(type_alias_impl_trait)] -#![feature(const_refs_to_cell)] -#![feature(const_result_drop)] -#![feature(const_trait_impl)] -#![feature(const_convert)] -#![feature(const_deref)] -#![feature(step_trait)] - mod constants; mod converters; mod flow; @@ -23,5 +13,5 @@ pub use flow::Flow; pub use hybrid::Hybrid; pub use link_type::LinkType; pub use links::{Error, Links, ReadHandler, WriteHandler}; -pub use point::Point; +pub use point::{Point, PointIter}; pub use query::{Query, ToQuery}; diff --git a/src/link_type.rs b/src/link_type.rs index 4c65df8..84e1d26 100644 --- a/src/link_type.rs +++ b/src/link_type.rs @@ -2,40 +2,35 @@ use funty::Unsigned; use std::{ convert::{TryFrom, TryInto}, fmt::Debug, - hint, - iter::Step, - marker::Destruct, }; -#[const_trait] +/// Trait for creating small numeric values from u8. +/// +/// This trait provides a convenient way to create numeric types from small u8 values, +/// which is commonly needed when working with link constants and indices. pub trait FuntyPart: Sized + TryFrom { + /// Create a value from a u8. Panics if the conversion fails. fn funty(n: u8) -> Self; } -// TryFrom has `Error = Infallible` for all types -impl> const FuntyPart for All { - fn funty(n: u8) -> Self - where - All: ~const Destruct, - >::Error: ~const Destruct, - { - // std `Result::unwrap_unchecked` is not const - match All::try_from(n) { - Ok(all) => all, - Err(_) => { - // >::Error is Infallible - unsafe { hint::unreachable_unchecked() } - } - } +impl> FuntyPart for All +where + >::Error: Debug, +{ + fn funty(n: u8) -> Self { + All::try_from(n).expect("conversion from u8 should succeed for all unsigned types") } } -// fixme: track https://github.com/rust-lang/rust/issues/67792 -#[const_trait] +/// Trait bound for numeric types that can be used as link identifiers. +/// +/// This trait combines several requirements: +/// - Must be an unsigned integer type (via `Unsigned`) +/// - Must support creation from small values (via `FuntyPart`) +/// - Must support conversions to/from various integer types pub trait LinkType: Unsigned + FuntyPart - + Step + TryFrom + TryFrom + TryFrom @@ -63,7 +58,7 @@ pub trait LinkType: { } -impl const LinkType for All where +impl LinkType for All where All: TryFrom + TryFrom + TryFrom diff --git a/src/point.rs b/src/point.rs index 5d79dfa..a981398 100644 --- a/src/point.rs +++ b/src/point.rs @@ -1,3 +1,7 @@ +/// A structure representing a single value repeated multiple times. +/// +/// This is useful for creating queries or representing repeated elements +/// without allocating a vector. pub struct Point { index: T, size: usize, @@ -47,11 +51,39 @@ impl Point { } } +/// Iterator for Point that yields copies of the stored value. +pub struct PointIter { + value: T, + remaining: usize, +} + +impl Iterator for PointIter { + type Item = T; + + fn next(&mut self) -> Option { + if self.remaining > 0 { + self.remaining -= 1; + Some(self.value) + } else { + None + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.remaining, Some(self.remaining)) + } +} + +impl ExactSizeIterator for PointIter {} + impl IntoIterator for Point { type Item = T; - type IntoIter = impl Iterator; + type IntoIter = PointIter; fn into_iter(self) -> Self::IntoIter { - (0..self.len()).map(move |_| self.index) + PointIter { + value: self.index, + remaining: self.size, + } } } diff --git a/tests/flow.rs b/tests/flow.rs index a614091..8888710 100644 --- a/tests/flow.rs +++ b/tests/flow.rs @@ -1,36 +1,37 @@ -#![feature(try_trait_v2)] - use platform_data::Flow; -use std::ops::{ControlFlow, FromResidual, Try}; +use std::ops::ControlFlow; #[test] fn basic() { let mut vec = vec![]; - (0..20).try_for_each(|i| { + for i in 0..20 { vec.push(i); - if i == 10 { Flow::Break } else { Flow::Continue } - }); + let flow = if i == 10 { Flow::Break } else { Flow::Continue }; + if flow.is_break() { + break; + } + } assert_eq!(vec, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); } #[test] -fn test_flow_continue() { +fn test_flow_continue_with_control_flow() { let mut count = 0; - (0..5).try_for_each(|_| { + let _ = (0..5).try_for_each(|_| { count += 1; - Flow::Continue + Flow::Continue.into_control_flow() }); assert_eq!(count, 5); } #[test] -fn test_flow_break_early() { +fn test_flow_break_early_with_control_flow() { let mut count = 0; - (0..100).try_for_each(|_| { + let _ = (0..100).try_for_each(|_| { count += 1; - Flow::Break + Flow::Break.into_control_flow() }); assert_eq!(count, 1); // Should stop after first iteration } @@ -40,16 +41,7 @@ fn test_from_control_flow_continue() { // Test conversion from ControlFlow::Continue let cf: ControlFlow = ControlFlow::Continue(()); let flow: Flow = cf.into(); - // We can't directly compare Flow values, but we can verify the conversion happened - // by checking behavior - the flow should be Continue - let result = std::iter::once(0).try_for_each(|_| { - let cf2: ControlFlow = ControlFlow::Continue(()); - let _: Flow = cf2.into(); - Flow::Continue - }); - // Verify the result is ok (iterator completed) - let _ = result; - let _ = flow; // Use the flow variable + assert!(flow.is_continue()); } #[test] @@ -57,46 +49,90 @@ fn test_from_control_flow_break() { // Test conversion from ControlFlow::Break let cf: ControlFlow<(), i32> = ControlFlow::Break(()); let flow: Flow = cf.into(); - let _ = flow; // Use the flow variable + assert!(flow.is_break()); // Verify Break behavior let mut count = 0; - (0..10).try_for_each(|_| { + let _ = (0..10).try_for_each(|_| { count += 1; - let cf2: ControlFlow<(), i32> = ControlFlow::Break(()); - let f: Flow = cf2.into(); - f + Flow::Break.into_control_flow() }); assert_eq!(count, 1); } #[test] -fn test_try_trait_from_output() { - // Test that Flow::from_output returns Continue - let flow = Flow::from_output(()); - let _ = flow; // Use the flow variable +fn from_control_flow() { + let continue_flow: Flow = ControlFlow::<(), ()>::Continue(()).into(); + assert!(continue_flow.is_continue()); - // Verify from_output behavior by testing in iteration - let mut count = 0; - std::iter::once(()).try_for_each(|_| { - count += 1; - Flow::from_output(()) - }); - assert_eq!(count, 1); + let break_flow: Flow = ControlFlow::<(), ()>::Break(()).into(); + assert!(break_flow.is_break()); } #[test] -fn test_from_residual() { - // Test FromResidual implementation - let flow: Flow = Flow::from_residual(Flow::Break); - let _ = flow; // Use the flow variable +fn into_control_flow() { + let cf: ControlFlow<()> = Flow::Continue.into(); + assert!(matches!(cf, ControlFlow::Continue(()))); - // Verify from_residual behavior - let mut count = 0; - (0..10).try_for_each(|_| { - count += 1; - Flow::from_residual(Flow::Break) + let cf: ControlFlow<()> = Flow::Break.into(); + assert!(matches!(cf, ControlFlow::Break(()))); +} + +#[test] +fn test_into_control_flow_method() { + // Test the into_control_flow() method + let continue_cf = Flow::Continue.into_control_flow(); + assert!(matches!(continue_cf, ControlFlow::Continue(()))); + + let break_cf = Flow::Break.into_control_flow(); + assert!(matches!(break_cf, ControlFlow::Break(()))); +} + +#[test] +fn test_flow_with_iterator() { + // Test using Flow with iterators via into_control_flow + let mut collected = vec![]; + let _ = (0..20).try_for_each(|i| { + collected.push(i); + if i == 10 { + Flow::Break.into_control_flow() + } else { + Flow::Continue.into_control_flow() + } }); - // from_residual(Break) returns Break, so should stop after first - assert_eq!(count, 1); + assert_eq!(collected, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); +} + +#[test] +fn test_is_continue() { + assert!(Flow::Continue.is_continue()); + assert!(!Flow::Break.is_continue()); +} + +#[test] +fn test_is_break() { + assert!(Flow::Break.is_break()); + assert!(!Flow::Continue.is_break()); +} + +#[test] +fn test_flow_copy() { + let flow = Flow::Continue; + let copied1 = flow; + let copied2 = flow; + assert_eq!(flow, copied1); + assert_eq!(flow, copied2); +} + +#[test] +fn test_flow_eq() { + assert_eq!(Flow::Continue, Flow::Continue); + assert_eq!(Flow::Break, Flow::Break); + assert_ne!(Flow::Continue, Flow::Break); +} + +#[test] +fn test_flow_debug() { + assert_eq!(format!("{:?}", Flow::Continue), "Continue"); + assert_eq!(format!("{:?}", Flow::Break), "Break"); } diff --git a/tests/hybrid.rs b/tests/hybrid.rs index ef4ff61..8710b97 100644 --- a/tests/hybrid.rs +++ b/tests/hybrid.rs @@ -76,7 +76,7 @@ fn test_hybrid_as_inner() { #[test] fn test_addr_to_raw_default() { - let converter = AddrToRaw::default(); + let converter = AddrToRaw; let result = converter.convert(100usize); // convert creates an external hybrid and returns its inner value assert!(result > 0); @@ -84,7 +84,7 @@ fn test_addr_to_raw_default() { #[test] fn test_raw_to_addr_default() { - let converter = RawToAddr::default(); + let converter = RawToAddr; let result = converter.convert(100usize); // convert creates an external hybrid and returns its abs value assert!(result > 0);