Rust
сооружения
Поиск…
Синтаксис
- struct Foo {field1: Type1, field2: Type2}
- let foo = Foo {field1: Type1 :: new (), field2: Type2 :: new ()};
- struct Bar (Тип1, Тип2); // тип tuple
- let _ = Bar (Type1 :: new (), Type2 :: new ());
- структура Баз; // Единичный тип
- let _ = Baz;
- пусть Foo {field1, ...} = foo; // извлекаем поле1 по образцу
- пусть Foo {field1: x, ..} = foo; // извлекаем поле1 как x
- let foo2 = Foo {field1: Type1 :: new (), .. foo}; // строить из существующих
- impl Foo {fn fiddle (& self) {}} // объявлять метод экземпляра для Foo
- impl Foo {fn tweak (& mut self) {}} // объявлять метод изменяемого экземпляра для Foo
- impl Foo {fn double (self) {}} // объявлять метод экземпляра экземпляра для Foo
- impl Foo {fn new () {}} // объявлять связанный метод для Foo
Определение структур
Структуры в Rust определяются с помощью ключевого слова struct
. Наиболее распространенная форма структуры состоит из набора именованных полей:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
Вышеупомянутое объявляет struct
с тремя полями: my_bool
, my_num
и my_string
, типа bool
, isize
и String
соответственно.
Другой способ создания struct
s в Rust - создать структуру кортежей :
struct Bar (bool, isize, String);
Это определяет новый тип, Bar
, который имеет три неназванных поля типа bool
, isize
и String
в этом порядке. Это называется шаблоном newtype , потому что он эффективно вводит новое «имя» для определенного типа. Однако он делает это более мощным образом, чем псевдонимы, созданные с использованием ключевого слова type
; Bar
здесь полностью функциональный, то есть вы можете написать для него свои собственные методы (см. Ниже).
Наконец, объявите struct
без полей, называемую структурно подобранной структурой :
struct Baz;
Это может быть полезно для насмешек или тестирования (когда вы хотите тривиально реализовать признак) или как тип маркера. В целом, однако, вы вряд ли столкнетесь со многими структурными единицами.
Обратите внимание: поля struct
в Rust являются закрытыми по умолчанию, то есть они не могут быть доступны из кода вне модуля, который определяет тип. Вы можете префикс поля с ключевым словом pub
чтобы сделать это поле общедоступным. Кроме того, struct
сама по себе тип является частным. Чтобы сделать тип доступным для других модулей, определение struct
также должно иметь префикс pub
:
pub struct X {
my_field: bool,
pub our_field: bool,
}
Создание и использование значений структуры
Рассмотрим следующие struct
определения:
struct Foo {
my_bool: bool,
my_num: isize,
my_string: String,
}
struct Bar (bool, isize, String);
struct Baz;
Построение новых структурных значений для этих типов является простым:
let foo = Foo { my_bool: true, my_num: 42, my_string: String::from("hello") };
let bar = Bar(true, 42, String::from("hello"));
let baz = Baz;
Поля доступа к структуре с использованием .
:
assert_eq!(foo.my_bool, true);
assert_eq!(bar.0, true); // tuple structs act like tuples
Изменчивое связывание с структурой может иметь свои мутации:
let mut foo = foo;
foo.my_bool = false;
let mut bar = bar;
bar.0 = false;
Возможности сопоставления шаблонов Rust также могут использоваться для просмотра внутри struct
:
// creates bindings mb, mn, ms with values of corresponding fields in foo
let Foo { my_bool: mb, my_num: mn, my_string: ms } = foo;
assert_eq!(mn, 42);
// .. allows you to skip fields you do not care about
let Foo { my_num: mn, .. } = foo;
assert_eq!(mn, 42);
// leave out `: variable` to bind a variable by its field name
let Foo { my_num, .. } = foo;
assert_eq!(my_num, 42);
Или создайте структуру, используя вторую структуру как «шаблон» с синтаксисом обновления Rust:
let foo2 = Foo { my_string: String::from("world"), .. foo };
assert_eq!(foo2.my_num, 42);
Методы структуры
Чтобы объявить методы в структуре (т. impl
Функции, которые можно называть «на» struct
или значения этого типа struct
), создайте блок impl
:
impl Foo {
fn fiddle(&self) {
// "self" refers to the value this method is being called on
println!("fiddling {}", self.my_string);
}
}
// ...
foo.fiddle(); // prints "fiddling hello"
&self
здесь указывает на неизменную ссылку на экземпляр struct Foo
чтобы вызвать метод fiddle
. Если бы мы захотели изменить экземпляр (например, изменить одно из его полей), мы вместо этого возьмем &mut self
(т. Е. Изменяемую ссылку):
impl Foo {
fn tweak(&mut self, n: isize) {
self.my_num = n;
}
}
// ...
foo.tweak(43);
assert_eq!(foo.my_num, 43);
Наконец, мы могли бы также использовать self
(обратите внимание на отсутствие &
) в качестве приемника. Это требует, чтобы экземпляр принадлежал вызывающему абоненту и заставил экземпляр перемещаться при вызове метода. Это может быть полезно, если мы хотим потреблять, уничтожать или иным образом полностью преобразовывать существующий экземпляр. Одним из примеров такого прецедента является обеспечение «цепных» методов:
impl Foo {
fn double(mut self) -> Self {
self.my_num *= 2;
self
}
}
// ...
foo.my_num = 1;
assert_eq!(foo.double().double().my_num, 4);
Обратите внимание, что мы также префикс self
с mut
чтобы мы могли мутировать себя, прежде чем возвращать его снова. Обратный тип double
метода также требует некоторого объяснения. Self
внутри блока impl
ссылается на тип, к impl
применяется impl
(в данном случае Foo
). Здесь в основном полезно сокращать, чтобы избежать повторной печати сигнатуры типа, но в свойствах он может использоваться для обозначения базового типа, реализующего определенный признак.
Чтобы объявить связанный метод (обычно называемый «метод класса» на других языках) для struct
просто оставить аргумент self
. Такие методы вызывают сам тип struct
, а не его экземпляр:
impl Foo {
fn new(b: bool, n: isize, s: String) -> Foo {
Foo { my_bool: b, my_num: n, my_string: s }
}
}
// ...
// :: is used to access associated members of the type
let x = Foo::new(false, 0, String::from("nil"));
assert_eq!(x.my_num, 0);
Обратите внимание, что методы структуры могут быть определены только для типов, объявленных в текущем модуле. Кроме того, как и в случае с полями, все методы структуры являются закрытыми по умолчанию и поэтому могут быть вызваны только кодом в том же модуле. Вы можете префиксные определения с ключевым словом pub
чтобы сделать их вызываемыми из другого места.
Общие структуры
Структуры могут быть сделаны универсальными по одному или нескольким параметрам типа. Эти типы указаны в <>
при обращении к типу:
struct Gen<T> {
x: T,
z: isize,
}
// ...
let _: Gen<bool> = Gen{x: true, z: 1};
let _: Gen<isize> = Gen{x: 42, z: 2};
let _: Gen<String> = Gen{x: String::from("hello"), z: 3};
Множественные типы могут быть заданы с помощью запятой:
struct Gen2<T, U> {
x: T,
y: U,
}
// ...
let _: Gen2<bool, isize> = Gen2{x: true, y: 42};
Параметры типа являются частью типа, поэтому две переменные одного и того же базового типа, но с разными параметрами, не являются взаимозаменяемыми:
let mut a: Gen<bool> = Gen{x: true, z: 1};
let b: Gen<isize> = Gen{x: 42, z: 2};
a = b; // this will not work, types are not the same
a.x = 42; // this will not work, the type of .x in a is bool
Если вы хотите написать функцию, которая принимает struct
независимо от назначения ее типа, эта функция также должна быть сделана общей:
fn hello<T>(g: Gen<T>) {
println!("{}", g.z); // valid, since g.z is always an isize
}
Но что, если мы хотим написать функцию, которая всегда может печатать gx
? Нам нужно было бы ограничить T
типом, который может отображаться. Мы можем сделать это с ограничениями типа:
use std::fmt;
fn hello<T: fmt::Display>(g: Gen<T>) {
println!("{} {}", g.x, g.z);
}
Функция hello
теперь определена только для экземпляров Gen
, тип T
которых реализует fmt::Display
. Если мы попытались передать в Gen<(bool, isize)>
например, компилятор будет жаловаться на то, что hello
не определено для этого типа.
Мы также можем использовать ограничения типов непосредственно по параметрам типа struct
чтобы указать, что вы можете только построить эту struct
для определенных типов:
use std::hash::Hash;
struct GenB<T: Hash> {
x: T,
}
Любая функция, которая имеет доступ к GenB
теперь знает, что тип x
реализует Hash
и, следовательно, может вызывать .x.hash()
. Множественные ограничения типа для одного и того же параметра можно задать, разделив их на +
.
То же, что и для функций, границы типов могут быть размещены после <>
с использованием ключевого слова where
:
struct GenB<T> where T: Hash {
x: T,
}
Это имеет то же смысловое значение, но может упростить чтение и форматирование подписи при наличии сложных границ.
Параметры типа также доступны для методов экземпляра и связанных методов struct
:
// note the <T> parameter for the impl as well
// this is necessary to say that all the following methods only
// exist within the context of those type parameter assignments
impl<T> Gen<T> {
fn inner(self) -> T {
self.x
}
fn new(x: T) -> Gen<T> {
Gen{x: x}
}
}
Если у вас есть ограничения типа на Gen
's T
, они также должны отражаться в границах типа impl
. Вы также можете сделать ограничения impl
более жесткими, чтобы сказать, что данный метод существует только в том случае, если тип удовлетворяет определенному свойству:
impl<T: Hash + fmt::Display> Gen<T> {
fn show(&self) {
println!("{}", self.x);
}
}
// ...
Gen{x: 42}.show(); // works fine
let a = Gen{x: (42, true)}; // ok, because (isize, bool): Hash
a.show(); // error: (isize, bool) does not implement fmt::Display