Как стать автором
Обновить

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

Отличная статья, читал на одном дыхании. Пишите ещё! Единственное что не понял это функция toSoldier. Функция принимает на вход некий self, а в маппере передаёте ей школьника. Как компилятор вывел из него имя?

Функция находится в блоке impl Scholar. Соответственно, она в качестве первого аргумента может принимать:


  • self, т.е. self: Scholar,
  • &self, т.е. self: &Scholar,
  • &mut self, т.е. self: &mut Scholar,
  • и, если не ошибаюсь, аналогично Pin<Box<self>> и Pin<&mut self>, но это уже экзотика;
  • ну, или ничего из перечисленного — тогда это будет не метод, а ассоциированная функция (статический метод, если угодно), которая вызывается на типе, а не на объекте.

Не надо так (это означает, что в дизайне структур данных что-то пошло не так, единичные кейсы исключительные кейсы, когда это нужно):


Pin<Box<self>>

Да и Pin<T> — это экзотика из-за ее документации. Редкий новичок или поверхностный погруженец позволит съесть свой мозг чайной ложечкой.

Этот код уже валидный, потому что владелец по прежнему один (сначала first, потом third). При этом изменение владельца никак не повлияло на second, ссылка будет продолжать указывать куда указывала.

Нет, нет, нет. Не надо так. Простой контрпример:


struct X; //объявляем структуру

fn test_borrow_checker () -> X {
    let first = X; // создаём экземпляр
    let second: &X = &first; // не меняем владельца, а берём ссылку на значение
    let third = first; // забираем владение X, всё вроде бы окей
    let fourth = second; // упс, first исчез, ссылка невалидна!

    return third;
}

В Вашем примере всё работает не потому, что заимствование в second не затронуто перемещением, а потому, что оно попросту не используется, и borrow-checker — как Вы позже заметили применительно к уникальным (изменяемым) заимствованиям — достаточно умный, чтобы это заметить.

Да, вы правы, и параграф после этого кода соответственно тоже не точный. Постараюсь исправить сегодня. Спасибо)
А можете в таком же духе рассказать про Box, Arc, Pin?
Про Box и Arc — они довольно простые, по идее могу. А вот с Pin самому надо доразобраться, так что это хороший повод написать.
По-этому просто iter() и работа с двойной ссылкой

Не обязательно загромождать код оператором разыменования, тк в rust есть deref coercion и компилятор может автоматически разыменовывать ссылки (примерно как в c++):


fn good_idea() {
    let scholars: Vec<Scholar> = Vec::new();
    let girls_c = scholars.iter().filter(|s| s.sex == Sex::Female).count();
    let boys_c = scholars.iter().filter(|s| s.sex == Sex::Male).count();
}

Аналогично в других кусках кода.

О! А это объясняет почему такая «кривлять» как двойное разыменованием на ровном месте есть в языке. Программист его все равно не заметит.


Правда я так и не понял зачем нужна ссылка на ссылку… они же немутабельные.

Тут скорее не "зачем", а "почему". Следите за руками:


  • Нужен итератор, который не требует отдавать ему целиком вектор. Для этого нужно, чтобы он получал не элементы вектора, а только ссылки на них.
  • Нужен адаптер для итератора, который не требует отдавать ему элементы исходного итератора (они ещё пригодятся дальше в цепочке). Для этого нужно, чтобы он получал не элементы итератора, а ссылки на них.

Оба этих требования не зависят ни от типа элементов вектора, ни от типа элементов в итераторе — даже несмотря на то, что в некоторых случаях можно просто копировать исходные элементы (и это реально будет делаться, если в цепочку вставить copied()), сигнатуры методов Vec::iter() и Iterator::filter() остаются одни и те же. Отсюда и двойная ссылка — потому что есть две независимые причины эти самые ссылки навесить.

Можно просто писать ссылку в аргументе лямбды. Так: |&s| ..., Тогда внутри лямбды уже можно точно без разименовывания.

Спасибо! Очень просто и интересно написано. Продолжайте в том же духе!
спасибо, приятно такое видеть)
Автор, прошу еще статей на rust :)

Статья огонь, а можно точно также рассказать про lifetime?

Если вы понимаете английский, то рекомендую вот это видео. По идее после его просмотра и некоторого количества самостоятельных экспериментов вопросов остаться не должно.
Вообще, имхо, последняя версия rustbook'а довольно неплохо это объясняет. Лучше первой. Правда в русском переводе есть настолько синтетический и неестественный текст, что иногда вникать тяжело, но в принципе понятно.
Если интересно, то могу продолжить. Варианты тем:
Голосую за веб-сервис. API самое очевидное — Conduit. В идеале без макросов ).
Может быть, но апи там довольно сложный — много работы всё это имплементить.
А пока не написал статью вам может быть интересна эта статья: docs.qovery.com/guides/tutorial/create-a-blazingly-fast-api-in-rust-part-1
Либо мой небольшой репозиторий with actix + diesel: github.com/invis87/ebbinghaus_memory_service
И там и там все на макросах. Это слишком легко написать, но ИМХО сложно объяснить/разобраться что происходит «под капотом».

Просто интересно. Сложность C++ часто заключается в том, что программист явно не видит что подразумевается под операцией присваивания или передачи параметра. Чтобы понять что будет делать код, нужно знать не только тип переменной, но и то, как этот тип может быть обработан, причем и операция присваивания может быть разная если она переопределена, и имена методов зачастую идентичны, а делают разное в зависимости от типа входных значений. И все это происходит неявно.

В Rust сделана та же самая ошибка: программист опять не понимает, что происходит при присваивании. Опять ему приходится следить за типом. Единственное что дает Rust - да, он может отслеживать все допустимые действия основываясь на концепции владения.

Но самая главная задача не решена: язык никак не помогает программисту понять, что происходит при присваивании.

Вопрос: почему постоянно пытаются скрыть, что происходит под капотом, если в этом состоит сама суть проблемы? Почему не делают синтаксис, в котором присваивание с копированием значения имеет один оператор, присваивание указателя - другой оператор, работа со ссылкой - третий? Тогда само выражение показывало бы что на самом деле происходит с данными.

Почему этого не делают?

программист опять не понимает, что происходит при присваивании

memcpy. Всегда. Всё остальное - оптимизации.

memcpy. Всегда.

Хочите сказать, что при копировании строк и массивов всегда происходит memcpy? И вообще вы не написали про какой язык говорите.

Естественно. Для строк (и векторов) копируются три usize-а, массив копируется целиком.

Вы так и не сообщили про какой язык говорите.

Для строк (и векторов) копируются три usize-а

Это ни о чем не говорит. Нас интересуют данные, которые лежат в строке или векторе. Они бывают сильно больше трех usize. Так как, будет для них использоваться memcpy?

массив копируется целиком.

Посмотрим на легаси плюсиков:

int a[1000];

много строк

int *b;

много строк

b=a;

И как, целиком скопировался массив? Выражение b=a не говорит ничего о том, что будет происходить. Будет ли memcpy() применятся к элементам? Судя по вашим утверждениям, обязательно будет.

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

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

Публикации

Изменить настройки темы

Истории