Comments 12
impl Rectangle {
fn new() -> Rectangle {
Rectangle { width: 0, height: 0 }
}
fn set_width(&mut self, width: u32) -> &mut Rectangle {
self.width = width;
self
}
fn build(self) -> Rectangle {
self
}
}
Жесть, на курсах такой же говнокод учите писать? Самое смешное, что вы даже не проверили код, он не рабочий:
let rect = Rectangle::new().set_width(10).set_height(20).build();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ------- value moved due to this method call
| |
| move occurs because value has type `Rectangle`, which does not implement the `Copy` trait
Ну и до кучи, чтобы мутировать поля достаточно просто добавить mut, к чему эти пляски с недобилдером были?
let rect = Rectangle::new().set_width(10).set_height(20).build();
// rect.width = 15; // ошибка, так как rect неизменяемый после создания
let mut rect = Rectangle::new().set_width(10).set_height(20).build();
rect.width = 20; // ой, мутировали
Тут скорее хотели показать приватные поля в структуре. Но надо было структуру переместить в другой файл/модуль (ключевое слово mod
):
// сама структура публичная, а поля - приватные
#[derive(Debug)]
pub struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
/// Других способов создать объект не существует
pub fn new() -> Self {
Self { width: 0, height: 0 }
}
}
Если бы хотели - так бы и написали. Тут скорее хотели порекламировать свои курсы, но забыли, что раст они не знают. Как можно воспринимать это всерьёз, если они пишут вот такое:
Если создается экземпляр структуры с помощью
let
, все его поля будут неизменяемыми, если только каждое поле явно не объявлено какmut
Явно видно непонимание иммутабельности. К примеру,
let rect = Rectangle::new().set_width(10).set_height(20).build();
// rect.width = 15; // ошибка, так как rect неизменяемый после создания
Во‐первых, код не работает. Вы не можете скормить .build()
&mut self
вместо self
, а именно первое возвращает .set_height
.
Во‐вторых, build
полностью бесполезна. Иммутабельность относится к конкретному месту размещения, если вы владеете объектом, то вы всегда можете сделать его мутабельным. Реальная иммутабельность достигается инкапсуляцией и удалением из общего доступа методов, которые могут изменить состояние объектов. Соответственно в шаблоне создатель у вас должен был быть тип RectangleBuilder и тип Rectangle, и только первый должен иметь методы set_width
.
Соответственно, говорить «создадим иммутабельную структуру», не объясняя нигде, что она иммутабельная только пока в общем доступе нет методов, её изменяющих, некорректно. Напомню, что вот это вполне себе корректный код:
let mut rect = Rectangle::new();
rect.set_width(10);
let rect = rect.build();
let rect = rect; // То же, что и строчка выше.
// rect.set_width(20); // Не скомпилируется.
let mut rect = rect;
rect.set_width(30); // А теперь мы опять можем менять rect.
Остальная часть не лучше. Например:
Если создается экземпляр структуры с помощью let, все его поля будут неизменяемыми, если только каждое поле явно не объявлено как mut:
Можете показать, как вы объявляете поля структуры как mut
? Только так, чтобы результат компилировался.
Или «создадим глобально доступный Arc
»: «код, в котором Arc нигде не упоминается».
Иммутабельность в Rust в нескольких предложениях:
Если вы не объявили что‐то, как
mut
, то взятие исключающей (&mut
) ссылки вам недоступно, как и всё, что требует её взятия.Изменение значения требует такой ссылки (или возможности её взятия) или внутренней мутабельности в каком‐то виде.
Исключающая ссылка гарантирует только, что объект больше никому не доступен. Разделяемая ссылка (
&
) гарантирует только, что на объект нет исключающих ссылок. Иммутабельность здесь вообще не причём, ссылки исторически назвали неправильно, но большинство типов компилятор позволит менять только по исключающей ссылке.
// А теперь мы опять можем менять rect.
В смысле "можем менять rect"?? Вы же в курсе, что let mut rect = rect
создаёт новый объект и мувает в него старый, а не снимает с объекта константность? Выведите на печать &rect
до и после выражения с let, они будут разными.
Это с точки зрения семантики языка. С точки зрения программиста это часто один и тот же объект, т.к. нет видимой разницы (разные адреса, если оптимизатор почему‐то не смог убрать перемещение, не считаются). Это особенно верно, если под rect
у нас не простая структура данных, а что‐то вроде Box<Rectangle>
: кого волнует, где в стеке указатель на Rectangle
, если данные всё равно за указателем и никуда не двигаются?
После некоторого размышления у меня появилось сильное подозрение, что семантика let foo = bar;
— это «создать новое место foo
и переместить в него объект из bar
» (объект один и тот же, но его переместили), а не «создать новый объект foo
и переместить в него данные из bar
, bar
удалить» (есть два разных объекта с непересекающимися временами жизни).
Я не могу найти конкретных подтверждений в документации (но то, как про перемещения объектов значений рассказывает документация std::pin
оставляет ощущение, что я скорее прав), но let foo = foo
точно не запускает Drop::drop
и при этом никаких конструкторов в Rust нет. Можете обосновать утверждение «let mut rect = rect
создаёт новый объект»?
Скорее создаёт новое имя, для существующего объекта. Чтобы создать место, имя нужно запинить.
это «создать новое место foo и переместить в него объект из bar» (объект один и тот же, но его переместили)
Именно так. Nomicon например явно говорит об этом: https://doc.rust-lang.org/nomicon/constructors.html. Мб я не так выразился, я имел в виду что объект всё же создаётся не полностью эквивалентный. У него другая позиция в памяти, +на старый объект после мува инвалидируются ссылки. "let mut rect = rect" это буквально то же самое, что "let mut rect2 = rect".
А зачем fn new() в таком виде если есть трейт Default?
let rect = Rectangle{width: 10, height: 20}; // короче и понятнее
let rect = Rectangle{width: 20, ..rect}; // не мутировали, но в тему статье
Присоединяйтесь к бесплатному курсу по Rust на платформе Stepik :)
Rust и иммутабельность