Types, Traits, Generics
Позволяет пользователям создавать свои собственные типы и избегать дублирования кода.
Типы и трейты
Типы
- Набор значений с заданной семантикой, компоновкой, …
Тип | Значение |
---|---|
u8 | { 0$_u$$_8$, 1$_u$$_8$, …, 255$_u$$_8$ } |
char | { ‘a’, ‘b’, … ’🦀’ } |
struct S(u8, char) | { (0$_u$$_8$, ‘a’), … (255$_u$$_8$, ’🦀’) } |
Эквивалентность типов и преобразования
- Это может быть не очевидно, но u8, &u8, &mut u8 полностью отличаются друг от друга.
- Любой t: T принимает только значения именно из T, например: — f(0_u8) не может быть вызван с помощью f(&0_u8). — f(&mut my_u8) не может быть вызван с помощью f(&my_u8). — f(0_u8) не может быть вызван с помощью f(0_i8). 0 != 0 работает (в математическом смысле), когда дело доходит до типов! В языковом смысле операция == (0u8, 0u16) просто не определена для предотвращения несчастных случаев.
Тип | Значение |
---|---|
u8 | { 0$_u$$_8$, 1$_u$$_8$, …, 255$_u$$_8$ } |
u16 | { 0u$_u$$_1$$_6$, 1$_u$$_1$$_6$, …, 65_535$_u$$_1$$_6$ } |
&u8 | { 0xffaa$_&$$_u$$8$, 0xffbb$&$$_u$$_8$, … }` |
&mut u8 | { 0xffaa$_&$$_m$$_u$$_t$ $_u$$8$, 0xffbb$&$$_m$$_u$$_t$ $_u$$_8$, … } |
- Тем не менее, Rust иногда может помочь конвертировать между типами$^1$:
— приводит к ручному преобразованию значений типов
0_i8 as u8
. — автоматическое преобразование типов, если безопасно$^2$, пусть x:&u8 = &mut 0_u8
; $^1$ Преобразуют значения из одного типа (например, u8) в другой (например, u16), возможно добавляя для этого инструкции процессора и этим отличаются от подтипов, которые подразумевают, что тип и подтип являются частью одного и того же множества (например, u8 является подтипом u16, а 0_u8 является тем же, что и 0_u16), где такое преобразование будет чисто проверкой времени компиляции. Rust не использует подтипизацию для обычных типов (и 0_u8 отличается от 0_u16), вроде как для пожизненно. $^2$ Безопасность здесь заключается не только в физическом понятии (например, &u8 не может быть преобразовано к &u128), «история показала, что такое преобразование приведет к ошибкам».
Реализации — impl S { }
impl Port {
fn f() { … }
}
- Типы обычно поставляются с собственными реализациями, например, impl Port {}, связавается с типом:
— связанная функция
Port::new(80)
. — методport.close()
То, что считается родственным, является более философским, чем техническим, ничто (кроме хорошего вкуса) не помешает сделать u8::play_sound().
Трейты — trait T { }
-
Трейты … — являются способом «абстрактного» поведения. — автор трейта заявляет семантически, что этот трейт означает X. — другие могут реализовать («подписаться на») поведение для своего типа.
-
Подумайте о трейтах как «списке методов» для типов:
Трейты как таблицы характеристик Self относится к типу.
-
Тот, кто входит в этот список, будет придерживаться поведения списка.
-
Трейты могут также включать в себя связанные методы, функции, …
trait ShowHex {
// Должны быть реализованы в соответствии с документацией.
fn as_hex() -> String;
// Предоставляется трейтом автора.
fn print_hex() {}
}
trait Copy { }
- Трейты без методов часто называют маркерными трейтами.
- Copy - это пример трейта маркера, означающий, что память может быть скопирована побитово.
- Некоторые черты трейты вне явного контроля.
- Sized, предоставляемый компилятором для типов с известным размером, либо это есть, либо его нет.
Реализация трейтов для типов - impl T for S { }
impl ShowHex for Port { … }
- Трейты реализуются для типов.
- Реализация предполагает, что A для B добавляет тип B в список принадлежности трейта:
- Визуально вы можете подумать о типе, получающем «значок» за свое членство:
Трейты против интерфейсов
Интерфейсы
- В Java Алиса создает интерфейс Eat.
- Когда Иван создаёт Venison, он должен решить, реализует ли Venison Eat или нет.
- Другими словами, все члены должны быть исчерпывающе объявлены во время определения типа.
- При использовании Venison Лена может использовать поведение, предоставляемое Eat:
// Лена импортирует 'Venison', чтобы создать ее, может использовать 'eat()', если она хочет.
import food.Venison;
new Venison("rudolph").eat();
Трейты
- В Rust Алиса создаёт трейт «Eat».
- Иван создаёт тип Venison и решает не реализовывать Eat (он может даже не знать о Eat).
- Кто-то$^*$ позже решит добавить Eat в Venison, это было бы действительно хорошей идеей.
- При использовании Venison Лена должна импортировать Eat отдельно:
// Лена должна импортировать 'Venison', чтобы создать его, и импортировать 'Eat' для метода трейта..
use food::Venison;
use tasks::Eat;
Venison::new("Гена").eat();
$^*$ Чтобы помешать двум лицам реализовать Eat по-разному Rust ограничивает этот выбор либо Алисой, либо Иваном, то есть impl Eat for Venison может произойти только в трейте Venison или в трейте Eat. Подробности см. в разделе Согласованность.
Дженерики
Type Constructors — Vec<>
Vec<u8>
- тип «вектор байтов»,Vec<char>
- тип «вектор символов», но что такоеVec < >
?
Конструкция | Значение |
---|---|
Vec<u8> | { [], [1], [1, 2, 3], … } |
Vec<char> | { [], [‘a’], [‘x’, ‘y’, ‘z’], … } |
Vec<> | - |
- Vec<> не является типом, не занимает память, даже не может быть преобразовано в код.
- Vec<> - конструктор типов, шаблон или рецепт для создания типов.
— позволяет третьей стороне строить конкретный тип через параметр.
— только тогда этот
Vec<UserType>
сам станет реальным типом.
Параметры дженериков — <T>
- Параметр для Vec<> часто называется T, поэтому
Vec<T>
. - T «имя переменной для типа» для подключения чего-либо определенного,
Vec<f32>
,S<u8>
,…
Тип конструктора | Реализация |
---|---|
struct Vec<T> {} | Vec<u8> , Vec<f32> , Vec<Vec<u8>> , … |
[T; 128] | [u8; 128] , [char; 128] , [Port; 128] … |
&T | &u8 , &u16 , &str , … |
Тип против конструкторов типов.
// S<> является конструктором типа с параметром T, пользователь может поставить любой тип для T.
struct S<T> {
x: T
}
// В коде для Т должен быть указан существующий тип.
fn f() {
let x: S<f32> = S::new(0_f32);
}
Константы дженерики — [T; N] и S<const N: usize>
- Некоторые конструкторы типов принимают не только определенный тип, но и определенную константу.
- [Т; n] конструирует тип массива, содержащий n раз T-тип.
- Для пользовательских типов, объявленных как MyArray<T, const N: usize>.
Тип конструктора | Реализация |
---|---|
[u8; N] | [u8; 0] , [u8; 1] , [u8; 2] , … |
struct S<const N: usize> {} | S<1> , S<6> , S<123> , … |
Конструкторы типов на основе константы.
let x: [u8; 4]; // "массив из 4-х байтов"
let y: [f32; 16]; // "массив из 16 чисел с плавающей точкой"
// `MyArray` является конструктором типа, требующим типа `T` и
// размер 'N' для построения конкретного типа.
struct MyArray<T, const N: usize> {
data: [T; N],
}
Границы (простые) — where T: X
- Если T может быть любым типом, то мы можем по рассуждать (писать код) для
Num<T>
? - Границы параметров: — ограничить допустимые типы (границы трейта) или значения (границы константы?) — теперь мы можем использовать эти ограничения!
- Ограничения трейтов действуют как «проверка членства»:
// Тип может быть создан только для некоторых «T», если
// T является частью «Абсолютного» списка членов.
struct Num<T> where T: Absolute {
…
}
Здесь мы добавляем границы к структуре. На практике вместо этого лучше добавлять границы к соответствующим блокам impl, см. далее этот раздел.
Границы (составные) — where T: X + Y*
struct S<T>
where
T: Absolute + Dim + Mul + DirName + TwoD
{ … }
- Длинный трейт может выглядеть устрашающе.
- На практике каждое дополнение + X к ограничению просто сокращает пространство подходящих типов.
Реализация - impl<>
Когда мы пишем:
impl<T> S<T> where T: Absolute + Dim + Mul {
fn f(&self, x: T) { … };
}
Когда мы пишем:
- вот рецепт реализации для любого типа T (
impl <T>
). - этот тип должен быть членом признаков Absolute + Dim + Mul.
- вы можете добавить блок реализации в семейство типов S<>.
- содержащиеся методы …
Можно считать, что такой код
impl<T>... {}
абстрактно реализует семейство моделей поведения. В частности, они позволяют третьим сторонам прозрачно материализовать реализации аналогично тому, как конструкторы типов материализуют типы:
// Если компилятор столкнется с этим, он
// - проверит '0' и 'x' соответствуют требованиям членства 'T'
// - создаст две новые версии 'f', одну для 'char', другую для 'u32'.
// - на основе предоставленной реализации
s.f(0_u32);
s.f('x');
Реализации общего покрытия - impl<T> X for T {…}
Можно также написать реализации, чтобы они применяли трейт ко многим типам:
// реализует Serialize для любого типа, если этот тип уже реализует ToHex
impl<T> Serialize for T where T: ToHex { … }
Это называется общими реализациями.
Что бы ни было в верхнем списке, может быть добавлено к нижнему списку, основываясь на следующем рецепте (impl).
Трейты могут быть безупречным способом обеспечения функциональность чужих типов модульным способом, если они просто реализуют другой интерфейс.
Расширенные концепции
Параметры трейтов — Trait<In> { type Out; }
Обратите внимание, как некоторые трейты могут быть использованы несколько раз, а другие только один раз.
Это почему?
- Сами трейты могут быть общими для двух видов параметров:
—
trait From<I> {}
—trait Deref { type O; }
- Помните, мы говорили, что трейты являются «списками членства» для типов и называются списком Self?
- Параметры I (для ввода) и O (для вывода)— это просто больше столбцов в списке этого трейта:
impl From<u8> for u16 {}
impl From<u16> for u32 {}
impl Deref for Port { type O = u8; }
impl Deref for String { type O = str; }
Входные и выходные параметры.
Теперь вот в чем дело:
- любые выходные параметры O должны быть однозначно определены входными параметрами I.
- (так же, как отношение X Y будет представлять функцию).
- Self в качестве входных данных.
Более сложный пример:
trait Complex<I1, I2> {
type O1;
type O2;
}
- создается связь типов с именем Complex.
- с 3 входными параметрами (Self всегда один) и 2 выходными, и он содержит (Self, I1, I2) => (O1, O2).
Различные реализации трейта. Последняя из них недопустима, (NiceMonster, u16, String) т.к. имеет одинаковую сигнатуру входных данных.
Рекомендации по разработке трейтов (аннотация)
- Выбор параметра (ввод и вывод) также определяет, кому может быть разрешено добавлять элементы: — Параметры I позволяют пересылать реализации пользователю (Леной). — Параметры O должны определяться программистом трейта (Алисой или Иваном).
trait A<I> { }
trait B { type O; }
// Реализатор добавляет (X, u32) к A.
impl A<u32> for X { }
// Реализатор добавляет impl. (X, ...) в A, пользователь может создавать объекты.
impl<T> A<T> for Y { }
// Конструктор обязан определить конкретную запись (X, O), добавленную к B.
impl B for X { type O = u32; }
Лена может добавить больше членов, предоставив свой собственный тип для T.
Для заданного набора входных данных (здесь Self) программист обязан предварительно выбрать O.
Рекомендации по разработке трейтов (пример)
Выбор параметров сопровождается заполнением трейта назначения.
Нет дополнительных параметров
trait Query {
fn search(&self, needle: &str);
}
impl Query for PostgreSQL { … }
impl Query for Sled { … }
postgres.search("SELECT …");
Автор трейта предполагает:
- ни реализатор, ни пользователь не должны настраивать API.
Входные параметры
trait Query<I> {
fn search(&self, needle: I);
}
impl Query<&str> for PostgreSQL { … }
impl Query<String> for PostgreSQL { … }
impl<T> Query<T> for Sled where T: ToU8Slice { … }
postgres.search("SELECT …");
postgres.search(input.to_string());
sled.search(file);
Автор трейта предполагает:
- реализатор настраивает API несколькими способами для одного типа Self.
- пользователи могут захотеть иметь возможность решать, для каких I-типов должно быть какое поведение.
Выходные параметры
trait Query {
type O;
fn search(&self, needle: Self::O);
}
impl Query for PostgreSQL { type O = String; …}
impl Query for Sled { type O = Vec<u8>; … }
postgres.search("SELECT …".to_string());
sled.search(vec![0, 1, 2, 4]);
Автор трейта предполагает:
- реализатор настраивает API для типа Self (но только одним способом).
- пользователям не нужно или не должно быть возможность влиять на настройку для конкретного Self.
Как вы можете видеть здесь, термин вход или выход не имеет (обязательно) никакого отношения к тому, являются ли I или O входами или выходами для фактической функции!
Несколько входных и выходных параметров
trait Query<I> {
type O;
fn search(&self, needle: I) -> Self::O;
}
impl Query<&str> for PostgreSQL { type O = String; … }
impl Query<CString> for PostgreSQL { type O = CString; … }
impl<T> Query<T> for Sled where T: ToU8Slice { type O = Vec<u8>; … }
postgres.search("SELECT …").to_uppercase();
sled.search(&[1, 2, 3, 4]).pop();
Как и примеры выше, автор трейта предполагает:
- пользователи могут захотеть иметь возможность решать, для каких типов I должны быть возможны.
- для входных данных, реализатор должен определить результирующий тип выходных данных.
Динамические типы / Типы нулевого размера
- Тип T имеет значение размера, если во время компиляции известно, сколько байт он занимает например u8, и &[u8], [u8] не имеют размера.
- Размером подразумевается
impl Sized for T {}
. Это происходит автоматически и не может быть вызвано пользователем. - Типы без размера называются динамически размерными типами (DST), иногда неразмерными.
- Типы без данных называются типами нулевого размера (ZST), не занимают места.
Пример | Комментарий |
---|---|
struct A { x: u8 } | Тип A имеет размер, т.е. impl Size for A , это «обычный» тип. |
struct B { x: [u8] } | Поскольку [u8] является DST, B в свою очередь, становится DST, то есть не подразумевает размер. |
struct C<T> { x: T } | Параметры типа имеют неявное ограничение T: Size, например, C<A> является допустимым, C<B> - нет. |
struct D<T: ?Sized> { x: T } | Использование ?Sized позволяет отказаться от этой привязки, т.е <B> . D также действителен |
struct E; | Тип E имеет нулевой размер и не будет потреблять память. |
trait F { fn f(&self); } | Трейты не имеют неявной границы размера, т.е. допустимо impl F for B {}. |
trait F: Sized {} | Трейты однако, могут выбирать размер через супер трейты. |
trait G { fn g(self); } | Для ‘Self’, такие параметры как DST impl все еще может выйти из строя, так как параметры не могут идти в стеке. |
?Sized
struct S<T> { … }
- T может быть любого типа.
- Тем не менее, существует невидимая привязка по умолчанию
T: Size
, поэтомуS<str>
невозможно из коробки. - Вместо этого мы должны добавить T : ?Sized, чтобы отказаться от этой привязки:
struct S<T> where T: ?Sized { … }
Дженерики и время жизни — <‘a>
- Время жизни действует$^*$ как параметры типа:
— пользователь должен указать определенный тип ‘a (компилятор поможет в методах).
— как
Vec<f32>
иVec<u8>
являются разными типами, так иS<'p>
иS<'q>
разные. --- это означает, что вы не можете просто присвоить значение типаS<'a>
переменной, ожидающейS<'b>
(исключение: отношение «подтип» для жизненных периодов жизни, например, ‘a переживет ‘b).
- ‘static — это только именуемый экземпляр времени жизни пространства типов.
// `'a является свободным параметром (cможет пройти любое конкретное время жизни)
struct S<'a> {
x: &'a u32
}
// В неуниверсальном коде 'static — это единственное именируемое время жизни,
// которое мы можем явно вставить здесь.
let a: S<'static>;
// В качестве альтернативы, в необщемом коде мы можем (часто должны) опустить 'a
// и заставить Rust автоматически определить правильное значение для 'a.
let b: S;
$^*$ Есть тонкие различия, например, вы можете создать явный экземпляр 0 типа u32, но за исключением «статических, вы не можете действительно создать время жизни», компилятор сделает это за вас.
Примечание для себя и TODO: эта аналогия кажется несколько ошибочной, как будто S<'a>
относится к S<'static>
как S<T>
к S<u32>
, статический будет типом, но тогда каково значение этого типа?
Внешние типы и трейты
Визуальный обзор типов и трейтов в вашем крейте.
Примеры трейтов и типов, и какие трейты можно реализовать для какого типа.
Преобразования типов
Как получить B, когда у вас есть A?
Введение
fn f(x: A) -> B {
// Как вы можете получить B от A?
}
Метод | Комментарий |
---|---|
Идентичность | Тривиальный случай, B — это именно A. |
Вычисление | Создание экземпляра B и управление им путем написания кода, преобразующего данные. |
Приведение | Преобразование по требованию между типами, где рекомендуется соблюдать осторожность. |
Принуждение | Автоматическое преобразование в рамках «ослабляющего набора правил».$^1$ |
Подтипизация | Автоматическое преобразование в наборе правил «одинаковый макет— разный срок службы».$^1$ |
$^1$ В то время как оба преобразуют A
в B
, принуждение, обычно ссылается на несвязанный B
(тип «можно разумно ожидать, что у него разные методы»), в то время как подтипизация ссылок на B
отличается только временем жизни.
Вычисления (трейты)
fn f(x: A) -> B {
x.into()
}
Сахар, чтобы получить B от A. Некоторые трейты обеспечивают канонические, вычислимые пользователем отношения типов:
Трейт | Пример | Комментарий |
---|---|---|
impl From<A> for B {} | a.into() | Очевидное, всегда допустимое отношение. |
impl TryFrom<A> for B {} | a.try_into()? | Очевидное, иногда действительное отношение. |
impl Deref for A {} | *a | A является интеллектуальным указателем несущим B , также допускает принуждение. |
impl AsRef<B> for A {} | a.as_ref() | A можно рассматривать как B . |
impl AsMut<B> for A {} | a.as_mut() | A можно рассматривать как мутабельное B . |
impl Borrow<B> for A {} | a.borrow() | A позаимствовал аналог B (ведет себя так же как Eq, …). |
impl ToOwned for A { … } | a.to_owned() | A владеет аналогом B . |
Приведение типа
fn f(x: A) -> B {
x as B
}
Преобразование типов с ключевым словом, как будто преобразование относительно очевидно, но может вызвать проблемы.
A | B | Пример | Комментарий |
---|---|---|---|
Ptr | Ptr | device_ptr as *const u8 | Если *A , *B имеют размер. |
Ptr | Integer | device_ptr as usize | |
Integer | Ptr | my_usize as *const Device | |
Number | Number | my_u8 as u16 | Часто удивительное поведение. |
enum w/o поля | Integer | E::A as u8 | |
bool | Integer | true as u8 | |
char | Integer | 'A' as u8 | |
&[T; N] | *const T | my_ref as *const u8 | |
fn(…) | Ptr | f as *const u8 | Если Ptr имеет Sized(размер). |
fn(…) | Integer | f as usize |
Где Ptr, Integer, Number просто используются для краткости и фактически означают:
- Ptr любой
*const T
или*mut T
. - Целое число любого типа u8 … i128.
- Вещественное число (f32, f64). Мнение — Приведение, особенно Число — Число, может легко пойти не так. Если вы обеспокоены правильностью, рассмотрите более явные методы.
Принуждение
fn f(x: A) -> B {
x
}
Автоматическое ослабление типа от А до В; Типы могут быть существенно разными.$^1$
A | B | Комментарий |
---|---|---|
&mut T | &T | Ослабление указателя. |
&mut T | *mut T | - |
&T | *const T | - |
*mut T | *const T | - |
&T | &U | Deref, если impl Deref<Target=U> for T. |
T | U | Unsized, если impl CoerceUnsized<U> for T .$^2$ |
T | V | Транзитивность, если T связывается с U и U к V. |
|x| x + x | fn(u8) -> u8 | Замыкание без захвата до эквивалентного указателя fn. |
$^1$ По существу, можно ожидать, что результат принуждения В
будет совершенно другого типа (т.е. иметь совершенно другие методы), чем исходный тип А
.
$^2$ Не вполне работает в примере выше, так как неразмерные не могут быть в стеке; представьте себе f(x: &A) - > &B
вместо этого. Отмена размера работает по умолчанию для:
[T; n]
для[T]
.T
дляdyn Trait
еслиimpl Trait for T {}
.Foo<…, T, …>
дляFoo<…, U, …>
при загадочных обстоятельствах.
Подтипизация
fn f(x: A) -> B {
x
}
Автоматически преобразует A в B для типов, отличающихся только временем жизни - примеры подтипизации:
A | B | Комментарий |
---|---|---|
&'static u8 | &'a u8 | Допустимый, вечный указатель также является переходным указателем. |
&'a u8 | &'static u8 | 🛑 Недействительный, преходящий не должен быть вечным. |
&'a &'b u8 | &'a &'b u8 | Действительно, то же самое. Но теперь все становится интересным. Читайте дальше. |
&'a &'static u8 | &'a &'b u8 | Допустимо, &'static u8 также &'b u8 , ковариантный внутри &. |
&'a mut &'static u8 | &'a mut &'b u8 | 🛑 Недействительный и неожиданный, инвариант внутри &mut . |
Box<&'static u8> | Box<&'a u8> | Действителен, Box вечный также Box с переходным процессом, ковариант. |
Box<&'a u8> | Box<&'static u8> | 🛑 Недействительный, Box с переходным может быть не вечной. |
Box<&'a mut u8> | Box<&'a u8> | 🛑 Неверно, см. таблицу ниже, &mut u8 никогда не был &u8 . |
Cell<&'static u8> | Cell<&'a u8> | 🛑 Недействительно, Cell никогда не является чем-то другим, инвариант. |
fn(&'static u8) | fn(&'a u8) | 🛑 Если fn требуется навсегда, он может подавиться переходными процессами. |
fn(&'a u8) | fn(&'static u8) | Питается переходными процессами может быть(!) вечной. |
for<'r> fn(&'r u8) | fn(&'a u8) | Тип с более высоким рейтингом для <'r> fn(&'r u8) также является fn(&'a u8) . |
🛑 это не примеры подтипизации:
A | B | Комментарий |
---|---|---|
u16 | u8 | 🛑 Явно недействителен, u16 никогда не должен автоматически быть u8. |
u8 | u16 | 🛑 Недействителен по замыслу, типы различные данные, даже если бы они могли преобразоваться. |
&'a mut u8 | &'a u8 | 🛑 Троянский конь, не подтип, а принуждение (все еще работает, только не подтипирование). |
Различные
fn f(x: A) -> B {
x
}
Автоматически преобразует A в B для типов, отличающихся только временем жизни - правила дисперсии подтипов:
- Более длинная продолжительность жизни ‘a, которая переживает более короткий ‘b, является подтипом ‘b.
- Подразумевает, что
'static
является подтипом всех других жизней'a
. - Независимо от того, являются ли типы с параметрами (например, &‘a T) подтипами друг друга, используется следующая таблица дисперсии:
Конструкция | 'a | T | U |
---|---|---|---|
&'a T | ковариант | ковариант | |
&'a mut T | ковариант | инвариант | |
Box<T> | ковариант | ||
Cell<T> | инвариант | ||
fn(T) -> U | контравариант | ковариант | |
*const T | ковариант | ||
*mut T | инвариант |
Ковариант означает, что если A
является подтипом B
, то T[A]
является подтипом T[B]
.
Контравариант означает, что если A
является подтипом B
, то T[B]
является подтипом T[A]
.
Инвариант означает, что даже если A
является подтипом B
, ни T[A]
, ни T[B]
не будут подтипом другого.
Соединения, такие как структура S<T> {}
, получают дисперсию через используемые ими поля, обычно становясь инвариантными, если смешиваются множественные дисперсии.
Другими словами, «регулярные» типы никогда не являются подтипами друг друга (например, u8 не является подтипом u16), и Box<u32>
никогда не будет под- или супертипом чего-либо. Однако, как правило Box<A>
, может быть подтипом Box<B>
(через ковариацию), если A
является подтипом B
, что может произойти только в том случае, если A
и B
являются «своего рода одним и тем же типом, который различался только по времени жизни», например, A
существует в &'static u32
и B
- &'a u32
.