rust/src/libcore
bors ed084b0b83 Auto merge of #69659 - CAD97:step-rework-take-3, r=Amanieu
Rework the std::iter::Step trait

Previous attempts: #43127 #62886 #68807
Tracking issue: #42168

This PR reworks the `Step` trait to be phrased in terms of the *successor* and *predecessor* operations. With this, `Step` hopefully has a consistent identity that can have a path towards stabilization. The proposed trait:

```rust
/// Objects that have a notion of *successor* and *predecessor* operations.
///
/// The *successor* operation moves towards values that compare greater.
/// The *predecessor* operation moves towards values that compare lesser.
///
/// # Safety
///
/// This trait is `unsafe` because its implementation must be correct for
/// the safety of `unsafe trait TrustedLen` implementations, and the results
/// of using this trait can otherwise be trusted by `unsafe` code to be correct
/// and fulful the listed obligations.
pub unsafe trait Step: Clone + PartialOrd + Sized {
    /// Returns the number of *successor* steps required to get from `start` to `end`.
    ///
    /// Returns `None` if the number of steps would overflow `usize`
    /// (or is infinite, or if `end` would never be reached).
    ///
    /// # Invariants
    ///
    /// For any `a`, `b`, and `n`:
    ///
    /// * `steps_between(&a, &b) == Some(n)` if and only if `Step::forward(&a, n) == Some(b)`
    /// * `steps_between(&a, &b) == Some(n)` if and only if `Step::backward(&a, n) == Some(a)`
    /// * `steps_between(&a, &b) == Some(n)` only if `a <= b`
    ///   * Corollary: `steps_between(&a, &b) == Some(0)` if and only if `a == b`
    ///   * Note that `a <= b` does _not_ imply `steps_between(&a, &b) != None`;
    ///     this is the case wheen it would require more than `usize::MAX` steps to get to `b`
    /// * `steps_between(&a, &b) == None` if `a > b`
    fn steps_between(start: &Self, end: &Self) -> Option<usize>;

    /// Returns the value that would be obtained by taking the *successor*
    /// of `self` `count` times.
    ///
    /// If this would overflow the range of values supported by `Self`, returns `None`.
    ///
    /// # Invariants
    ///
    /// For any `a`, `n`, and `m`:
    ///
    /// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, m).and_then(|x| Step::forward_checked(x, n))`
    ///
    /// For any `a`, `n`, and `m` where `n + m` does not overflow:
    ///
    /// * `Step::forward_checked(a, n).and_then(|x| Step::forward_checked(x, m)) == Step::forward_checked(a, n + m)`
    ///
    /// For any `a` and `n`:
    ///
    /// * `Step::forward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::forward_checked(&x, 1))`
    ///   * Corollary: `Step::forward_checked(&a, 0) == Some(a)`
    fn forward_checked(start: Self, count: usize) -> Option<Self>;

    /// Returns the value that would be obtained by taking the *successor*
    /// of `self` `count` times.
    ///
    /// If this would overflow the range of values supported by `Self`,
    /// this function is allowed to panic, wrap, or saturate.
    /// The suggested behavior is to panic when debug assertions are enabled,
    /// and to wrap or saturate otherwise.
    ///
    /// Unsafe code should not rely on the correctness of behavior after overflow.
    ///
    /// # Invariants
    ///
    /// For any `a`, `n`, and `m`, where no overflow occurs:
    ///
    /// * `Step::forward(Step::forward(a, n), m) == Step::forward(a, n + m)`
    ///
    /// For any `a` and `n`, where no overflow occurs:
    ///
    /// * `Step::forward_checked(a, n) == Some(Step::forward(a, n))`
    /// * `Step::forward(a, n) == (0..n).fold(a, |x, _| Step::forward(x, 1))`
    ///   * Corollary: `Step::forward(a, 0) == a`
    /// * `Step::forward(a, n) >= a`
    /// * `Step::backward(Step::forward(a, n), n) == a`
    fn forward(start: Self, count: usize) -> Self {
        Step::forward_checked(start, count).expect("overflow in `Step::forward`")
    }

    /// Returns the value that would be obtained by taking the *successor*
    /// of `self` `count` times.
    ///
    /// # Safety
    ///
    /// It is undefined behavior for this operation to overflow the
    /// range of values supported by `Self`. If you cannot guarantee that this
    /// will not overflow, use `forward` or `forward_checked` instead.
    ///
    /// # Invariants
    ///
    /// For any `a`:
    ///
    /// * if there exists `b` such that `b > a`, it is safe to call `Step::forward_unchecked(a, 1)`
    /// * if there exists `b`, `n` such that `steps_between(&a, &b) == Some(n)`,
    ///   it is safe to call `Step::forward_unchecked(a, m)` for any `m <= n`.
    ///
    /// For any `a` and `n`, where no overflow occurs:
    ///
    /// * `Step::forward_unchecked(a, n)` is equivalent to `Step::forward(a, n)`
    #[unstable(feature = "unchecked_math", reason = "niche optimization path", issue = "none")]
    unsafe fn forward_unchecked(start: Self, count: usize) -> Self {
        Step::forward(start, count)
    }

    /// Returns the value that would be obtained by taking the *successor*
    /// of `self` `count` times.
    ///
    /// If this would overflow the range of values supported by `Self`, returns `None`.
    ///
    /// # Invariants
    ///
    /// For any `a`, `n`, and `m`:
    ///
    /// * `Step::backward_checked(a, n).and_then(|x| Step::backward_checked(x, m)) == n.checked_add(m).and_then(|x| Step::backward_checked(a, x))`
    /// * `Step::backward_checked(a, n).and_then(|x| Step::backward_checked(x, m)) == try { Step::backward_checked(a, n.checked_add(m)?) }`
    ///
    /// For any `a` and `n`:
    ///
    /// * `Step::backward_checked(a, n) == (0..n).try_fold(a, |x, _| Step::backward_checked(&x, 1))`
    ///   * Corollary: `Step::backward_checked(&a, 0) == Some(a)`
    fn backward_checked(start: Self, count: usize) -> Option<Self>;

    /// Returns the value that would be obtained by taking the *predecessor*
    /// of `self` `count` times.
    ///
    /// If this would overflow the range of values supported by `Self`,
    /// this function is allowed to panic, wrap, or saturate.
    /// The suggested behavior is to panic when debug assertions are enabled,
    /// and to wrap or saturate otherwise.
    ///
    /// Unsafe code should not rely on the correctness of behavior after overflow.
    ///
    /// # Invariants
    ///
    /// For any `a`, `n`, and `m`, where no overflow occurs:
    ///
    /// * `Step::backward(Step::backward(a, n), m) == Step::backward(a, n + m)`
    ///
    /// For any `a` and `n`, where no overflow occurs:
    ///
    /// * `Step::backward_checked(a, n) == Some(Step::backward(a, n))`
    /// * `Step::backward(a, n) == (0..n).fold(a, |x, _| Step::backward(x, 1))`
    ///   * Corollary: `Step::backward(a, 0) == a`
    /// * `Step::backward(a, n) <= a`
    /// * `Step::forward(Step::backward(a, n), n) == a`
    fn backward(start: Self, count: usize) -> Self {
        Step::backward_checked(start, count).expect("overflow in `Step::backward`")
    }

    /// Returns the value that would be obtained by taking the *predecessor*
    /// of `self` `count` times.
    ///
    /// # Safety
    ///
    /// It is undefined behavior for this operation to overflow the
    /// range of values supported by `Self`. If you cannot guarantee that this
    /// will not overflow, use `backward` or `backward_checked` instead.
    ///
    /// # Invariants
    ///
    /// For any `a`:
    ///
    /// * if there exists `b` such that `b < a`, it is safe to call `Step::backward_unchecked(a, 1)`
    /// * if there exists `b`, `n` such that `steps_between(&b, &a) == Some(n)`,
    ///   it is safe to call `Step::backward_unchecked(a, m)` for any `m <= n`.
    ///
    /// For any `a` and `n`, where no overflow occurs:
    ///
    /// * `Step::backward_unchecked(a, n)` is equivalent to `Step::backward(a, n)`
    #[unstable(feature = "unchecked_math", reason = "niche optimization path", issue = "none")]
    unsafe fn backward_unchecked(start: Self, count: usize) -> Self {
        Step::backward(start, count)
    }
}
```

Note that all of these are associated functions and not callable via method syntax; the calling syntax is always `Step::forward(start, n)`. This version of the trait additionally changes the stepping functions to talk their arguments by value.

As opposed to previous attempts which provided a "step by one" method directly, this version of the trait only exposes "step by n". There are a few reasons for this:

- `Range*`, the primary consumer of `Step`, assumes that the "step by n" operation is cheap. If a single step function is provided, it will be a lot more enticing to implement "step by n" as n repeated calls to "step by one". While this is not strictly incorrect, this behavior would be surprising for anyone used to using `Range<{primitive integer}>`.
- With a trivial default impl, this can be easily added backwards-compatibly later.
- The debug-wrapping "step by n" needs to exist for `RangeFrom` to be consistent between "step by n" and "step by one" operation. (Note: the behavior is not changed by this PR, but making the behavior consistent is made tenable by this PR.)

Three "kinds" of step are provided: `_checked`, which returns an `Option` indicating attempted overflow; (unsuffixed), which provides "safe overflow" behavior (is allowed to panic, wrap, or saturate, depending on what is most convenient for a given type); and `_unchecked`, which is a version which assumes overflow does not happen.

Review is appreciated to check that:

- The invariants as described on the `Step` functions are enough to specify the "common sense" consistency for successor/predecessor.
- Implementation of `Step` functions is correct in the face of overflow and the edges of representable integers.
- Added tests of `Step` functions are asserting the correct behavior (and not just the implemented behavior).
2020-05-15 11:24:50 +00:00
..
alloc Rollup merge of #71492 - LeSeulArtichaut:document-unsafe-2, r=Mark-Simulacrum 2020-04-24 02:47:38 +02:00
array Add comment for Ord implementation for array 2020-05-06 17:02:53 +08:00
benches Use assoc float consts in libcore 2020-04-06 22:44:51 +02:00
char Stabilize UNICODE_VERSION (feature unicode_version) 2020-04-23 14:36:30 +02:00
convert Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
fmt Replace max/min_value() with MAX/MIN assoc consts 2020-04-03 09:33:10 +02:00
future Rollup merge of #70834 - yoshuawuyts:future-pending-ready, r=sfackler 2020-05-09 03:10:01 +02:00
hash Add a note about fat pointers 2020-04-23 23:05:37 +02:00
iter Auto merge of #69659 - CAD97:step-rework-take-3, r=Amanieu 2020-05-15 11:24:50 +00:00
macros Update src/libcore/macros/mod.rs 2020-04-29 12:16:32 -07:00
mem Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
num Auto merge of #69659 - CAD97:step-rework-take-3, r=Amanieu 2020-05-15 11:24:50 +00:00
ops rewrite Drop documentation 2020-05-07 09:10:31 +02:00
prelude Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
ptr Document unsafety for *const T and *mut T 2020-05-01 19:49:56 +02:00
slice slice::fill: take T by value. 2020-05-02 20:15:05 +02:00
str Rollup merge of #71366 - faern:use-assoc-int-consts3, r=dtolnay 2020-04-22 10:34:06 +09:00
sync Use copy bound in atomic operations to generate simpler MIR 2020-03-18 00:47:08 +01:00
task Use matches macro in libcore and libstd 2020-01-08 07:10:28 +03:00
tests Auto merge of #69659 - CAD97:step-rework-take-3, r=Amanieu 2020-05-15 11:24:50 +00:00
unicode Stabilize UNICODE_VERSION (feature unicode_version) 2020-04-23 14:36:30 +02:00
any.rs Map to -> return. 2020-05-12 16:54:29 +01:00
ascii.rs Format libcore with rustfmt 2019-11-26 23:02:11 -08:00
bool.rs Format libcore with rustfmt (including tests and benches) 2019-12-06 20:20:51 -08:00
borrow.rs Format the world 2019-12-22 17:42:47 -05:00
Cargo.toml Move the matches! macro to the prelude 2019-10-23 15:35:36 +02:00
cell.rs Mention RefCell::take can panic in docs 2020-05-03 12:52:23 +02:00
clone.rs Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
cmp.rs Fix doc link to Eq trait from PartialEq trait 2020-04-29 12:06:32 -04:00
default.rs Fix typo in Default trait docs: Provides -> Provide 2020-04-16 22:39:00 +01:00
ffi.rs Make VaList::arg link actually work 2020-04-23 11:06:46 -04:00
hint.rs Document unsafety in core::{panicking, alloc::layout, hint, iter::adapters::zip} 2020-04-24 01:48:48 +02:00
internal_macros.rs Require issue = "none" over issue = "0" in unstable attributes 2019-12-21 13:16:18 +02:00
intrinsics.rs document stable counterparts of intrinsics 2020-04-29 15:50:21 +02:00
lib.rs Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
marker.rs Use min_specialization in liballoc 2020-04-26 16:27:13 +01:00
option.rs Update src/libcore/option.rs 2020-05-13 21:13:35 +08:00
panic.rs document stable counterparts of intrinsics 2020-04-29 15:50:21 +02:00
panicking.rs Bump bootstrap compiler 2020-04-25 09:25:33 -04:00
pin.rs fix various typos 2020-03-06 15:19:31 +01:00
primitive.rs Bump core::primitive to 1.43 2020-02-23 23:59:39 -08:00
raw.rs Minor follow-up after renaming librustc(_middle) 2020-04-03 19:03:13 +09:00
result.rs docs: make the description of Result::map_or more clear 2020-04-04 14:18:02 +03:00
time.rs Stop accessing module level int consts via crate::<Ty> 2020-04-20 23:38:06 +02:00
tuple.rs Format libcore with rustfmt 2019-11-26 23:02:11 -08:00
unit.rs Format libcore with rustfmt 2019-11-26 23:02:11 -08:00