More traits

I am wondering what would be your thoughts on having more traits directly into the std.

Can easily imagine some like

  • Numeric: any numeric value

  • Integer: Numeric: any integer, also give operation on integers

  • UnsignedInteger: Integer: give specific operations on unsigned

  • SignedInteger: Integer: gives specific operations on signed

  • Float: Numeric: gives specific operations on floats

  • Iterable<Iterable: Iterator<T>>: allow access to the iter() method

  • IntoIterable<IntoIterable: IntoIterator<T>>: allow access to the into_iter() method

  • Container: allow access to the len(), is_empty(), etc. method

and I think many more can be found. This would allow for truly generic bounds without add dependencies, especially for the Iterable, IntoIterable and Container one that would allow generic bounds without having to resort to AsRef<&[...]> or other workaround

Numeric trait hierarchies are hard, and a big design space. The standard library is in the unique position of being the only library that can really never say “nope, this other API design would be much better, let’s change it in version 2.0”, so keeping such traits in third-party crates that do have that ability is beneficial.

For a slightly longer piece on these aspects, check out e.g. this blog post:

I don’t really understand – at all – what new abstraction you propose with the IntoIterable one… into_iter is already exclusively a trait method :man_shrugging:. The iter() method is mostly convenience, and in generic context can already be readily replaced by an IntoIterator bound on &T, so something like &T: IntoIterator<…> or perhaps for<'l> &'l T: IntoIterator can model some T: Iterable-like functionality. Similarly, at least for iterable collections, you can get access to len() and is_empty() methods with ExactSizeIterator.

E.g.

use std::collections::VecDeque;
use std::collections::HashSet;

fn main() {
    let a = [1, 2, 3, 4, 5];
    let b = HashSet::from(["foo", "bar", "baz"]);
    let c = VecDeque::<()>::new();

    inspect_len(&a, "a");
    inspect_len(&b, "b");
    inspect_len(&c, "c");
}

fn inspect_len<'a, T: ?Sized>(x: &'a T, name: &str)
where
    &'a T: IntoIterator<IntoIter: ExactSizeIterator>,
{
    println!("len of `{name}`: {}", x.into_iter().len());
}
3 Likes

There have been many proposals to add a "numeric tower" of traits into the standard library, as well as some container abstraction traits. For the most part we'd love to do this, though the design will take some work.

Some of the main things we'd likely want to have before starting on such an endeavor:

  • The ability to implement supertraits via subtraits (e.g. an impl Subtrait for Type block can define all the methods that are part of a supertrait and end up with an automatic implementation of the supertrait). This allows us to split out traits later if we get them wrong, and present traits as fine-grained while also grouping them into useful logical sets.
  • The ability to have inherent traits. Not a hard requirement, but it would be nicer if we didn't have to write a pile of forwarding methods from trait-to-inherent or inherent-to-trait, and could instead write these methods once, on the corresponding trait impl, and have that trait be inherent on the type.
  • Supertrait item shadowing. This is particularly important if we add a lot of new methods that may conflict with methods in the ecosystem.

All of these things are on the todo list.

6 Likes

I think https://github.com/rust-lang/rfcs/pull/3686 would be a great start to that.

It would change the story from "man, I have to make a macro for all the integer types" to just "oh yeah, I have one generic implementation for unsigned integers, and coherence doesn't think that overlaps with the impl for the signed ones".

Because with just the traits, even if we had Josh already mentioned, the first thing people are going to do is try to implement something for all SignedIntegers and UnsignedIntegers, and get told that doesn't work because of coherence overlap.


TBH, I'm in general skeptical of the "one true numeric tower" anyway. The obvious way you make traits for "look, at most I want to use this with maybe some v3fs for graphics" traits would be very different from the "I'm trying to do things in my Computer Algebra System" and from "actually what I'm trying to do is let people plug in their own matrix libraries for solving these thousands-of-variables problems".

4 Likes

The numeric traits are in the num crate. Integers are kind of magic in Rust because integer literals have some type inference applied to them, e.g. in [0_u32, 1] the 1 is inferred to be a u32. But you can't use this with the num traits, which makes writing code generic over integers hard to write (try to write a generic function that computes x + 10).

completly forgat about this one =) thanks I will read its documentation !!! thanks for your explained response =)