Co to jest „podstawowy typ” w Rust?

37

Gdzieś wybrałem termin „typ podstawowy” (i jego atrybut #[fundamental]) i właśnie teraz chciałem dowiedzieć się o nim więcej. Niejasno pamiętam, że chodziło o rozluźnienie zasad koherencji w niektórych sytuacjach. I myślę, że typy referencyjne są takimi podstawowymi typami.

Niestety wyszukiwanie w Internecie nie doprowadziło mnie zbyt daleko. Odniesienie do Rust nie wspomina o tym (o ile widzę). Właśnie znalazłem problem z tworzeniem podstawowych typów krotek i RFC, które wprowadziły ten atrybut . Jednak RFC ma jeden akapit na temat podstawowych typów:

  • #[fundamental]Typ Footo taki, w którym realizuje Impl kocem Foojest niszczące zmiany. Jak opisano &i &mutsą fundamentalne. Ten atrybut zostałby zastosowany Box, dzięki czemu Box zachowuje się tak samo, jak &i &mutw odniesieniu do spójności.

Uważam to sformułowanie za dość trudne do zrozumienia i wydaje mi się, że potrzebuję dogłębnej wiedzy o pełnej wersji RFC, aby zrozumieć ten fragment o podstawowych typach. Miałem nadzieję, że ktoś może wyjaśnić podstawowe typy w nieco prostszy sposób (oczywiście bez zbytniego uproszczenia). To pytanie służyłoby również jako łatwo dostępna wiedza.

Aby zrozumieć podstawowe typy, chciałbym odpowiedzieć na następujące pytania (oprócz głównego pytania „co to w ogóle są?”):

  • Czy typy podstawowe mogą zrobić coś więcej niż typy inne niż podstawowe?
  • Czy jako autor biblioteki mogę w jakiś sposób skorzystać z oznaczenia niektórych moich typów jako #[fundamental]?
  • Jakie typy z podstawowego języka lub standardowej biblioteki są fundamentalne?
Lukas Kalbertodt
źródło

Odpowiedzi:

34

Zwykle, jeśli biblioteka ma typ ogólny Foo<T>, dalsze skrzynki nie mogą zaimplementować na niej cech, nawet jeśliT jest to typ lokalny. Na przykład,

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

Na konkretny przykład, który działa na placu zabaw (czyli daje błąd),

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

(plac zabaw)


Zwykle umożliwia to autorowi skrzynek dodawanie (ogólnych) implementacji cech bez rozbijania dalszych skrzyń. Jest to świetne w przypadkach, gdy początkowo nie jest pewne, czy typ powinien implementować określoną cechę, ale później staje się jasne, że powinien. Na przykład, możemy mieć jakiś typ liczbowy, który początkowo nie implementuje cech z num-traits. Te cechy mogą być dodane później, bez potrzeby przełomowej zmiany.

Jednak w niektórych przypadkach autor biblioteki chce, aby skrzynie niższego rzędu mogły same implementować cechy. To gdzie#[fundamental] pojawia się atrybut. Po umieszczeniu na typie, żadna cecha, która nie jest obecnie zaimplementowana dla tego typu, nie zostanie zaimplementowana (z wyjątkiem zmiany przełomowej). W rezultacie skrzynki odbiorcze mogą implementować cechy tego typu, o ile parametr typu ma charakter lokalny (istnieją pewne skomplikowane zasady decydowania o tym, które parametry typu się liczą). Ponieważ typ podstawowy nie wdroży danej cechy, cechę tę można swobodnie wdrożyć bez powodowania problemów ze spójnością.

Na przykład Box<T>jest zaznaczony #[fundamental], więc działa następujący kod (podobny do Rc<T>powyższej wersji). Box<T>nie implementuje Default(chyba że Timplementuje Default), więc możemy założyć, że nie będzie to w przyszłości, ponieważ Box<T>jest fundamentalne. Zauważ, że implementacja Defaultdla Barspowodowałaby problemy, ponieważ Box<Bar>już wtedy implementuje Default.

struct Bar;

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

(plac zabaw)


Z drugiej strony cechy mogą być również oznaczone #[fundamental]. Ma to podwójne znaczenie dla typów podstawowych. Jeśli jakikolwiek typ nie implementuje obecnie fundamentalnej cechy, można założyć, że ten typ nie wdroży jej w przyszłości (ponownie, z wyjątkiem przełomowej zmiany). Nie jestem do końca pewien, jak to się stosuje w praktyce. W kodzie (link poniżej) FnMutjest zaznaczony jako podstawa z uwagą, że jest potrzebny do wyrażenia regularnego (coś o &str: !FnMut). Nie mogłem znaleźć, gdzie jest używany w regexskrzyni lub czy jest używany gdzie indziej.

Teoretycznie, gdyby Addcechę oznaczono jako fundamentalną (co zostało omówione), można by ją zastosować do implementacji dodawania między rzeczami, które jeszcze jej nie mają. Na przykład dodawanie [MyNumericType; 3](punktowe), które może być przydatne w niektórych sytuacjach (oczywiście, uczynienie [T; N]podstawowymi również by na to pozwoliło).


Prymitywne podstawowe typy &T, &mut T(patrz tutaj dla demonstracji wszystkich ogólnych prymitywnych typów). W standardowej bibliotece, Box<T>a Pin<T>także oznaczone jako fundamentalne.

Podstawowe cechy w standardowej biblioteki są Sized, Fn<T>, FnMut<T>, FnOnce<T>i Generator.


Pamiętaj, że ten #[fundamental]atrybut jest obecnie niestabilny. Problem ze śledzeniem to numer 29635 .

SCappella
źródło
1
Świetna odpowiedź! Jeśli chodzi o prymitywne typy: istnieją tylko garstka generic prymitywne typy: &T, &mut T, *const T, *mut T, [T; N], [T], fnwskaźnik i krotki. A testowanie ich wszystkich (proszę powiedz mi, jeśli ten kod nie ma sensu) wydaje się, że referencje są jedynymi podstawowymi typami pierwotnymi . Ciekawy. Byłbym zainteresowany poznaniem powodów, dla których inni nie są, szczególnie surowych wskazówek. Ale chyba nie jest to zakres tego pytania.
Lukas Kalbertodt
1
@LukasKalbertodt Dzięki za informacje na temat typów pierwotnych. Dodałem w twoich testach. Jeśli chodzi o uzasadnienie dotyczące referencji a wskaźników, sprawdź ten komentarz w żądaniu ściągnięcia RFC.
SCappella,
Odwołanie nie dokumentuje niestabilnych atrybutów, dlatego go tam nie znalazłeś.
Havvy,