Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is a "fundamental type" in Rust?

Tags:

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):

  • Can fundamental types do more than non-fundamental ones?
  • Can I, as a library author, benefit in some way from marking some of my types as #[fundamental]?
  • Which types from the core language or standard library are fundamental?
like image 562
Lukas Kalbertodt Avatar asked Nov 24 '19 20:11

Lukas Kalbertodt


1 Answers

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). I'm not exactly sure how this is used in practice. In the code (linked below), FnMut is marked fundamental with the note that it's needed for regex (something about &str: !FnMut). I couldn't find where it's used in the regex crate or if it's used elsewhere.

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.

like image 111
SCappella Avatar answered Nov 13 '22 15:11

SCappella