Что такое «фундаментальный тип» в Rust?

37

Где-то я подобрал термин «фундаментальный тип» (и его атрибут #[fundamental]) и только сейчас я хотел узнать о нем больше. Я смутно помню, что в некоторых ситуациях мне нужно было ослабить правила согласованности. И я думаю, что ссылочные типы являются такими фундаментальными типами.

К сожалению, поиск в Интернете не дал мне очень далеко. Ссылка на Rust не упоминает об этом (насколько я вижу). Я только что обнаружил проблему создания фундаментальных типов кортежей и RFC, который ввел атрибут . Однако в RFC есть один параграф о фундаментальных типах:

  • #[fundamental]Тип Fooявляется один , где реализуется одеяло осущ через Fooэто разрывное изменение. Как описано, &и &mutявляются фундаментальными. Этот атрибут будет применяться к Box, заставляя Box вести себя так же, как &и в &mutотношении согласованности.

Я нахожу формулировку довольно трудной для понимания, и мне кажется, что мне нужны глубокие знания полного RFC, чтобы понять это немного о фундаментальных типах. Я надеялся, что кто-нибудь сможет объяснить фундаментальные типы в несколько более простых терминах (конечно, без слишком большого упрощения). Этот вопрос также послужил бы легко доступным источником знаний.

Чтобы понять фундаментальные типы, я хотел бы ответить на эти вопросы (в дополнение к основному вопросу «что это вообще такое?», Конечно):

  • Могут ли фундаментальные типы сделать больше, чем неосновные?
  • Могу ли я, как автор библиотеки, получить какую-то выгоду от пометки некоторых моих типов как #[fundamental]?
  • Какие типы из основного языка или стандартной библиотеки являются фундаментальными?
Лукас Калбертодт
источник

Ответы:

34

Обычно, если библиотека имеет универсальный тип Foo<T>, нижестоящие ящики не могут реализовать свойства, даже если Tэто некоторый локальный тип. Например,

( 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)
    }
}

Для конкретного примера, который работает на детской площадке (то есть выдает ошибку),

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

(игровая площадка)


Это обычно позволяет автору ящика добавлять (скрывать) реализации черт, не ломая ящики ниже по течению. Это замечательно в тех случаях, когда изначально нет уверенности в том, что тип должен реализовывать определенную черту, но позже становится ясно, что это необходимо. Например, у нас может быть какой-то числовой тип, который изначально не реализует черты num-traits. Эти черты могут быть добавлены позже без необходимости изменения.

Однако в некоторых случаях автор библиотеки хочет, чтобы нижележащие ящики могли самостоятельно реализовывать признаки. Вот где #[fundamental]атрибут вступает в силу. Когда он помещен в тип, любая черта, в настоящее время не реализованная для этого типа, не будет реализована (за исключением критических изменений). В результате в нижележащих ящиках могут быть реализованы признаки для этого типа, если параметр типа является локальным (существуют некоторые сложные правила для определения того, какие параметры типа учитываются для этого). Поскольку базовый тип не реализует данную черту, эта черта может быть свободно реализована без проблем с согласованностью.

Например, Box<T>помечен #[fundamental], поэтому работает следующий код (аналогично Rc<T>версии выше). Box<T>не реализует Default(если не Tреализует Default), поэтому мы можем предположить, что в будущем этого не произойдет, потому что Box<T>это фундаментально. Обратите внимание, что реализация Defaultfor Barвызовет проблемы, поскольку Box<Bar>уже реализует Default.

struct Bar;

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

(игровая площадка)


С другой стороны, черты также могут быть отмечены #[fundamental]. Это имеет двойственное значение для основных типов. Если какой-либо тип в настоящее время не реализует фундаментальную черту, можно предположить, что этот тип не будет реализовывать его в будущем (опять же, за исключением критических изменений). Я не совсем уверен, как это используется на практике. В коде (ссылка ниже) FnMutпомечен как фундаментальный с пометкой, что это необходимо для регулярных выражений (кое-что о &str: !FnMut). Я не мог найти, где он используется в regexящике или где-то еще.

В теории, если Add черта была отмечена как фундаментальная (что уже обсуждалось), это можно использовать для реализации сложения между вещами, у которых ее еще нет. Например, добавление [MyNumericType; 3](точечно), которое может быть полезно в определенных ситуациях (конечно, [T; N]фундаментальное также позволит это).


Примитивные основные типы &T, &mut T(см здесь для демонстрации всех общих примитивных типов). В стандартной библиотекеBox<T> а Pin<T>также помечены как фундаментальные.

Основные черты в стандартной библиотеке Sized, Fn<T>, FnMut<T>,FnOnce<T> и Generator.


Обратите внимание, что #[fundamental]атрибут в настоящее время нестабилен. Проблема с отслеживанием - проблема № 29635 .

SCappella
источник
1
Отличный ответ! Что касается примитивных типов: существует лишь горстка родовой примитивные типов: &T, &mut T,*const T , *mut T, [T; N], [T], fnуказатель и кортежи. И тестируя их все (пожалуйста, скажите, если этот код не имеет смысла), кажется, что ссылки являются единственными фундаментальными примитивными типами . Интересно. Мне было бы интересно узнать причину, почему другие нет, особенно необработанные указатели. Но это не предмет этого вопроса, я полагаю.
Лукас Калбертодт
1
@LukasKalbertodt Спасибо за информацию о примитивных типах. Я добавил в ваших тестах. Что касается обоснования ссылок против указателей, проверьте этим комментарием в запросе RFC.
SCappella
Ссылка не документирует нестабильные атрибуты, поэтому вы ее там не нашли.
Havvy