Как стать автором
Поиск
Написать публикацию
Обновить

Комментарии 19

интерфейс внешних функций (foreign function interface, FFI) без накладных расходов

мягко говоря это неправда. Не бывает FFI без совсем накладных расходов.

Дефолтным целочисленным типом является

дефолтным он является если во первых влезает в размер, во вторых его не вывели откуда-то из других мест. Например

fn foo() -> usize {
  let x = 1248412;
  x
}

Автоматически выведет тип x как usize с момента объявления. Такой вывод работает с любыми типами, не только с целочисленными.

Для loop break может принимать опциональное выражение, которое становится значением выражения loop

what?

перегрузка функций в Rust не поддерживается

Это мягко говоря не правда. Перегрузка функций вполне может работать через типажи и шаблоны. Несколько иной тип полиморфизма, но тем не менее сигнатуры функций оказываются перегруженными.

Общие/распределенные (shared) ссылки доступны только для чтения:

Это не правда. Вплоне себе существует возможность мутабельного заисмствования

Rust не создает ссылки автоматически

Чтобы это вообще могло значить. В смысле не даёт функциям, занимающим значение автоматичски взять ссылку? То бишь

fn foo(val: &i32) { /**/ }

fn main() {
  let x = 33_i32;
  foo(x);  // не скомпилится т.к. заимствование явно не указаео
  foo(&x); // как должно быть чтобы всё собралось
}

Эксклюзивные

это уже какая-то собственная терминология попёрла. Есть изменяемые и неизменяемые ссылки/заимствования, аналогично изменяемым и неизменяемым переменным.

обеспечивает идентичность объекта (object identity):

Какое-то бессмысленное утверждение. О чём это вообще.

what?

Написано запутано, но ниже есть поясняющий пример же.

это уже какая-то собственная терминология попёрла

Терминология нестандартная, но широко распространённая.

Владение объектом бывает эксклюзивным и неэкслкюзивным, но это маленько про другой тип данных - Rc/Arc, Cell/RefCell и т.п.. Изменяемость это как раз про переменные и ссылки на них или на неизменяемые данные.

Да нет, к ссылкам эту терминология тоже применима.

Для начала, эта терминология полностью корректна: &-ссылки и правда заимствуют значение разделяемо, а &mut-сылки и правда заимствуют его эксклюзивно. Это, напомню, является частью модели памяти Rust.

Теперь почему многие предпочитают использовать классификацию "разделяемая/эксклюзивная" вместо "неизменяемая/изменяемая". Тут все просто: называть типы данных вроде &Cell<…> полным наименованием ("неизменяемая ссылка на Cell") долго, а попытка выкинуть вторую часть ведёт к ложным ассоциациям. Поэтому даже в документации Rust &-ссылки предпочитают называть разделяемыми.

А антонимом к "разделяемой" является именно "эксклюзивная", но никак не "изменяемая".

Написано запутано, но ниже есть поясняющий пример же.

А, у вас абзац сполз с примером и там сразу началось про continue, которого в этом блоке не оказалось, как в общем и отдельного примера для continue.

У нас?

У них!

Есть изменяемые и неизменяемые ссылки/заимствования, аналогично изменяемым и неизменяемым переменным.

А потом, когда человек уже немного погрузился в контекст, ему объясняют, что "изменяемый/неизменяемый" - упрощение, а на самом деле это "уникальный/неуникальный".

Уникальность это также как и с эксклюзивностью про уровни владения и про изменяемый доступ. Arc/Rc, например, позволяет шарить доступ, давай неуникальное владение объектом. Так что изменяемость, которая задаётся ключевым словом mut (от mutable - изменяемый) это ни разу не про уникальность/эксклюзивность.

Arc/Rc, например, позволяет шарить доступ, давай неуникальное владение объектом

Они как раз обеспечивают уникальность владения. Просто делают это на рантайме, а не при компиляции. Так же как и RefCell.

Если развернуто сказать, то &mut дает гарантию того, что в один момент времени ссылка на это место в памяти есть только в этом месте кода.

Разработчики назвали это mut, чтобы порог входа был ниже. Но на самом деле мутабельность – это просто следствие того, что ссылка только одна. Почитайте про interior mutability, там разъяснено подробнее. https://doc.rust-lang.org/book/ch15-05-interior-mutability.html

Они как раз обеспечивают уникальность владения

Rc.clone делает ровно то что уникальность делать не должна - копирует указатель на объект владения, позволяя позднее получать к нему доступ, как минимум на чтение из нескольких мест, а с RefCell ещё и на запись. Аналог в С++ имеет немного более понятное название - shared_pointer, то бишь совместный указатель. Под капотом занимается ровно тем же - подсчётом указателей - Reference counting aka Rc. Так что и Rc и Arc точно не занимаются уникальностью владения - этим занят borrow checker и это дефолтное поведение всех переменных в принципе.

Изменяемость aka mutability - это возможность изменить содержимое объекта/переменной и оно может присутствовать в Rc/Arc через тот же RefCell, но это не является обязательным. Ну и ключевой слово mut также позволяет задавать изменяемость конкретных переменных или ссылок на них. Семантика языка подразумевает, что в одну единицу времени может быть множество читателей, но единственный писатель, но это никак не связано с уникальностью доступа, как это видно в случае Rc/Arc - доступ множественный, но точка изменяемости остаётся единственной.

Пардон, я почему-то прочитал Arc/Rc, а в голове держал при этом Mutex/RefCell.

Мой поинт в основном был про то, что mut дает эксклюзивность доступа, а изменяемость это уже следствие.

Например, мы расшарили через Arc/Rc доступ к одному объекту. Он при этом не сможет быть mut, компилятор не позволит. Потому что нет эксклюзивности доступа.

Потом мы взяли и положили внутрь Arc/Rc какой-то из мьютексов. У нас пока нет ссылки на сам объект, только на мьютекс.

Но как только мы сделали lock() на мьютексе, мы получили в ответ уже mut-ссылку, потому что нам на рантайме гарантируется, что пока мьютекс залочен, существует только одна ссылка на этот объект, поэтому она и может быть mut. Как только она дропнется – кто-то другой сможет получить уже свою уникальную ссылку.

Примерно так же с RefCell, у нас нет ссылки на объект, только на сам RefCell. Когда мы вызываем borrow_mut(), на рантайме проверяется, есть ли сейчас другие ссылки, которые еще не дропнуты. Если они есть – мы не получим mut-ссылку. А если нет, то мы ее получим и пока не дропнем – никому другому RefCell не даст получить еще одну.

Поэтому я написал, что фраза про эксклюзивность у автора статьи вполне верна. Без unsafe мы не сможем получить ссылку на объект, если у кого-то уже есть &mut на него.

Под "уникальностью владения" я имел ввиду уникальность обладания &mut на объект, а не обладания самим объектом, криво выразился немного.

Мой поинт в основном был про то, что mut дает эксклюзивность доступа, а изменяемость это уже следствие.

Любая созданная переменная по-умолчанию имеет эксклюзивный доступ. Вы можете одалживать доступ к ней, даже если она не объявлена mut. Mut же объявляет изменяемость либо переменной, либо данных, на которые она указывает. Так, например, в аргумент функции приходится писать (mut arg: &mut <_>) - одно отвечает за изменяемость переменной. другое - за изменяемость данных по указателю. Если решали задачки по обходу деревьев наверняка сталкивались с этим.

Менять данные в безопасном контексте в один момент времени может только единственный писатель, но доступ к самим данным могут иметь несколько читателей, если задействуется соответствующий механизм синхронизации.

Когда мы вызываем borrow_mut(), на рантайме проверяется,

оно не даст скомпилироваться, если был нарушен порядок доступа, поэтому оно работает в comptime, а не runtime.

 Без unsafe мы не сможем получить ссылку на объект, если у кого-то уже есть &mut на него.

Да ну?

fn borrow_immutable(s: &String) {
    println!("{s}");
}
fn borrow_mutable(s: &mut String) {
    s.push('a');
    borrow_immutable(s); // тут у кого-то уже есть mut ссылка
}

fn main() {
    let mut asd = String::new();
    // тут asd имеет эксклюзивное (то есть исключительное)
    // владение объектом String. Он может одалживать доступ
    borrow_immutable(&asd);
    borrow_mutable(&mut asd);
    borrow_immutable(&asd);
    
    let qwe = asd;
    // теперь qwe имеет исключительный доступ и никто не может
    // иметь изменяемый доступ, пока владение не будет
    // отдано кому-то ещё
}

Под "уникальностью владения" я имел ввиду уникальность обладания &mut на объект, а не обладания самим объектом, криво выразился немного.

учитывая, что указатель не владеет данными, а только указывает на них странно называть это владением.

оно не даст скомпилироваться, если был нарушен порядок доступа, поэтому оно работает в comptime, а не runtime.

RefCell работает исключительно в рантайме, например: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=bbd501e232c54860aeeb5735ba2d299f

fn main() {
    let c = RefCell::new(String::from("test"));

    // Меняем значение по немутабельной ссылке
    change(&c);
    println!("{c:?}");

    // Борровинг немутабельной ссылки на рантайме
    let borrowed = c.borrow();
    println!("borrowed: {borrowed}");

    // Борровинг мутабельной ссылки на рантайме
    // ошибки компиляции не будет, но мы тут словим
    // панику при запуске, потому что переменная borrowed
    // еще не дропнулась
    let mut mut_borrowed = c.borrow_mut();
    mut_borrowed.push_str("-test");
}

fn change(c: &RefCell<String>) {
    let mut s = c.borrow_mut();
    s.push_str("-test");
}

Мы тут видим, что изменять данные можно даже если ссылка не объявлена как &mut и видим, что этот код компилируется, а проверка борровинга идет на рантайме.

Именно поэтому я говорю про "эксклюзивность" вместо "мутабельности", RefCell ломает концепцию мутабельности, позволяя менять данные. Безопасность памяти при этом обеспечивается тем, что RefCell проверяет правила борровинга на рантайме. Не выдаст мутабельную ссылку, если в данный момент существуют какие-то другие ссылки. Поддерживая при этом концепцию о том, что если мы имеем мутабельную ссылку, то в данный момент времени только мы работаем с этим местом в памяти.

Mut же объявляет изменяемость либо переменной, либо данных, на которые она указывает. Так, например, в аргумент функции приходится писать (mut arg: &mut <_>) - одно отвечает за изменяемость переменной. другое - за изменяемость данных по указателю.

Если мы следуем вашей фразе про объявление "изменяемости", то почему у меня в примере получилось изменить данные по ссылке, которую я не объявлял как &mut?

Да ну?

В вашем примере функции передают друг-другу ссылку, но в один момент времени эта ссылка находится только в одной из функций. Раст не позволит чтобы ссылки одновременно присутствовали в нескольких местах, пока существует мутабельная.

В вашем примере мы отдали выполнение в функцию borrow_immutable, пока она выполняется, в main ничего не происходит, ссылка находится внутри borrow_immutable. И так далее с остальными функциями.

RefCell работает исключительно в рантайме.

Динамическая проверка заимствований в пределах самого RefCell. Но у меня в статике оно ругалось, когда в условном цикле делал что-то вроде.

if let Some(cell) = blalba.get_cell() {
  let mut node = cell.borrow_mut(); // Err: referenced somewhere else 
  ... 
}

Точный код ошибки не скажу. Но при нормальном использовании оно вполне себе блочит доступ в comp time. В вашем примере эксклюзивность действительно имеет смысл, но опять же не в контексте статьи:

 что только эта ссылка может использоваться для доступа к значению

Доступ на чтение все ещё доступ к значению и концепция единого писателя и множественных читателей имеет место быть.

которую я не объявлял как &mut?

Потому что вы получили её посредством borrow_mut. Неизменность переменной не отменяет неизменности данных, отдаваемых из её интерфейса.

Опять еще одна статья про то как в расте создать тип int? Ну серьезно уже 3я за последний месяц статья-перевод просто документации. Напишите как сделать бэкенд на расте с дизелем/миграциями/сервисами/активх контроллерами/логами и все это запаковать в докер и раскидать правильно по файлам.
а как объявить функцию на расте - я и сам знаю)

не имеет среды выполнения

Чего-чего не имеет?

Среды выполнения — Execution environment — не имеет только кучка байтов у вас на хранилище. Но как только эта кучка байтов стала процессом (не в терминах ОС, а в смысле кода, который прямо сейчас исполняется CPU) — у неё есть среда выполнения. Даже если это bare metal.

Я догадываюсь, что автор имеет в виду кое-что другое. Но даже с этим кое-чем другим утверждение “Rust не имеет кое-чего другого” ложно. Без этого кое-чего другого даже приведённый Hello World не заработает. «Не иметь» и «Позволять не иметь» — сильно не одно и то же.

Добавлю, что хотя автоматический вывод типов переменных существует, для функций нужно явно прописывать все типы аргументов и возвращаемого значения. Такое сделано специально в дизайне языка, чтобы не переусложнять компилятор.

А ещё в расте существует так называемый возвратный полиморфизм:

let result: u32 = obj.func();

Будет вызываться та функция, которая возвращает тип данных u32, хотя функций без аргументов и с тем же именем может быть несколько (перегрузка методов), отличаются только типом возвращаемого значения.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий