rust

What is a "fundamental type" in Rust?


Somewhere I picked up the term "fundamental type" (and its attribute #[fundamental]) and just now I wanted to learn more about it. I vaguely remember it being about relaxing the coherence rules in some situations. And I think the reference types are such fundamental types.

Unfortunately, searching the web didn't bring me very far. The Rust reference does not mention it (as far as I can see). I just found an issue about making tuples fundamental types and the RFC that introduced the attribute. However, the RFC has a single paragraph about fundamental types:

  • A #[fundamental] type Foo is one where implementing a blanket impl over Foo is a breaking change. As described, & and &mut are fundamental. This attribute would be applied to Box, making Box behave the same as & and &mut with respect to coherence.

I find the wording fairly hard to understand and it feels like I need in-depth knowledge of the full RFC to understand this bit about fundamental types. I was hoping someone could explain fundamental types in somewhat simpler terms (without simplifying too much, of course). This question would also serve as an easy-to-find piece of knowledge.

To understand fundamental types, I'd like to answer these questions (in addition to the main "what are they even?" question, of course):


Solution

  • Normally, if a library has a generic type Foo<T>, downstream crates can't implement traits on it, even if T is some local type. For example,

    (crate_a)

    struct Foo<T>(pub t: T)
    

    (crate_b)

    use crate_a::Foo;
    
    struct Bar;
    
    // This causes an error
    impl Clone for Foo<Bar> {
        fn clone(&self) -> Self {
            Foo(Bar)
        }
    }
    

    For a concrete example that works on the playground (that is, gives an error),

    use std::rc::Rc;
    
    struct Bar;
    
    // This causes an error
    // error[E0117]: only traits defined in the current crate
    // can be implemented for arbitrary types
    impl Default for Rc<Bar> {
        fn default() -> Self {
            Rc::new(Bar)
        }
    }
    

    (playground)


    This normally enables the crate author to add (blanket) implementations of traits without breaking downstream crates. That's great in cases where it isn't initially certain that a type should implement a particular trait, but it later becomes clear that it should. For example, we might have some sort of numeric type that initially doesn't implement the traits from num-traits. Those traits could be added in later without needing a breaking change.

    However, in some cases, the library author wants downstream crates to be able to implement traits themselves. This is where the #[fundamental] attribute comes in. When placed on a type, any trait not currently implemented for that type won't be implemented (barring a breaking change). As a result, downstream crates can implement traits for that type as long as a type parameter is local (there are some complicated rules for deciding which type parameters count for this). Since the fundamental type won't implement a given trait, that trait can freely be implemented without causing coherence issues.

    For example, Box<T> is marked #[fundamental], so the following code (similar to the Rc<T> version above) works. Box<T> doesn't implement Default (unless T implements Default) so we can assume that it won't in the future because Box<T> is fundamental. Note that implementing Default for Bar would cause problems, since then Box<Bar> already implements Default.

    struct Bar;
    
    impl Default for Box<Bar> {
        fn default() -> Self {
            Box::new(Bar)
        }
    }
    

    (playground)


    On the other hand, traits can also be marked with #[fundamental]. This has a dual meaning to fundamental types. If any type doesn't currently implement a fundamental trait, it can be assumed that that type won't implement it in the future (again, barring a breaking change). Rust RFC #1023 has some explanation of how this is used in practice. In the code (linked below), FnMut is marked fundamental with the comment so that regex can rely that `&str: !FnMut`. The Regex::replace method lets the replacement be either a string or a function, and if not for that, then this wouldn't work because they would conflict.

    In theory, if the Add trait were marked fundamental (which has been discussed) this could be used to implement addition between things that don't already have it. For example, adding [MyNumericType; 3] (pointwise), which could be useful in certain situations (of course, making [T; N] fundamental would also allow this).


    The primitive fundamental types are &T, &mut T (see here for a demonstration of all the generic primitive types). In the standard library, Box<T> and Pin<T> are also marked as fundamental.

    The fundamental traits in the standard library are Sized, Fn<T>, FnMut<T>, FnOnce<T> and Generator.


    Note that the #[fundamental] attribute is currently unstable. The tracking issue is issue #29635.