Pull to refresh

Comments 23

Чтобы исправить ситуацию, мы сделали проверку умнее. Теперь она видит, когда переменная фактически завершила использовать значение. После этого она не блокирует использование данных.

Вызывает подозрительные чувства такая оптимизация. Если раньше можно было просто не задумываться о том, когда переменная, объявленная в начале блока станет доступна, теперь при чтении кода нужно за этим следить.
Да, может топорно, но хотя бы проще для понимания.

Вызывает подозрительные чувства такая оптимизация. Если раньше можно было просто не задумываться о том, когда переменная, объявленная в начале блока станет доступна, теперь при чтении кода нужно за этим следить.

За чем следить? Новый чекер пропускает всё то же, что и старый, просто разрешает больше ситуаций. То есть множество ситуаций, который он разрешает, строго больше старого.


По сути старый работал "переменная занята до конца блока", а новый "переменная занята до последнего использования".


fn main() {
    let mut x = 5;
    let y = &x;
    let mut z = &mut x;
}

тут переменная y никак не используется, и её можно спокойно удалить. Но это нам понятно, а старый бч не пропускал такой код. И это тривиальный сценарий, а нетривиальные привели к тому, что пришлось целый entry api например воротить над хэш мапами, потому что не получалось нормально в match использовать мутабельный или readonly борроу.

За чем следить?

За тем, где происходит последнее использование. Конец блока — это интуитивно понятнее и визуально проще отследить, нежели "последнее использование переменной где-то внутри блока".


Если смотреть на рафинированый пример то да, очевидно, что после второй строчки y никак не используется.
Но в реальности будет что-то такое:


fn main() {
    let mut x = 5;
    // какой-то код 1
    let y = &x;
    // какой-то код 2
    let mut z = &mut x;
    // какой-то код 3
}

И визуально может быть не слишком очевидно, где именно после let y = &x; заканчивается использование x.

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


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


Причем если раьнше такой пример можно было бы сделать просто расставив правильные скобки, то вот этот код без nll написать нельзя:


fn process_or_default() {
    let mut map = ...;
    let key = ...;
    match map.get_mut(&key) { // -------------+ 'lifetime
        Some(value) => process(value),     // |
        None => {                          // |
            map.insert(key, V::default()); // |
            //  ^~~~~~ ERROR.              // |
        }                                  // |
    } // <------------------------------------+
}
Для неиспользованных переменных все равно насыплет ворнингов да еще и clippy ругнется до кучи. Так что следить ручками не придется. Если конечно не злоупотреблять всякими сайленсерами.

Так я же не спорю, что БЧ всё отследит и ворнингов насыпет. Когда что-то пишешь сам — можно и положиться на линтеры.


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


При этом вещи вроде RAII и другие полезные приспособления нужны в том числе для того, чтобы наоборот не полагаться на внимательность и не терять время.

Меня тоже, если честно, немного тревожит этот аспект NLL — что оно более хитрое теперь все. Но 1) не так и просто придумать более-менее реалистичный пример, когда реально из-за этого сильно наступить на грабли 2) очень много людей просто отказывались пользоваться языком с до-NLL проверками, считая язык просто недоработанным (даже на хабре к прошлым статьям десятки таких коментов были).

Мне кажется изменение наоборот позволит упростить код и в некоторых случаях сделать его более понятным без тулинга, избегая ситуаций когда приходится смотреть кому и где я там одолжил свои указатели. Это как в той истории, где в городишко в отель заехал постоялец, сделал предоплату и хозяин отдал долг трактирщику, трактирщик сапожнику, сапожник отдал долг… молочница отдала долг хозяину. Постоялец передумал, забрал предоплату и уехал, а полгорода сократило долги.
С NLL это позволяет сокращать такие «долги» автоматически.
Мы сделали это более явным. Теперь если вы хотите обратиться к корневому крейту, то используете префикс crate::. Это лишь одно из улучшений для понятности.

Вот с этим я не понял. Был у меня такой код:
mod foo;
use foo::bar;

В 2018-mode этот код перестал компилироваться, чтобы заработало, пришлось делать так:
mod foo;
use crate::foo::bar;

Я что-то не так делаю или это какое-то неправильное улучшение?
Я что-то не так делаю

Всё так.


это какое-то неправильное улучшение?

Улучшение скорее правильное чем нет, но не полностью стабилизированное.


Если ничего неожиданного не случится, то в 1.32 будет полная версия т.н. "uniform paths" когда пути в импортах (use) относительные и резолвятся точно так же, как и во всех прочих местах.
(В то время как в Rust 2015 пути в импортах абсолютные и резолвятся относительно корня текущего крейта.)


В 1.31 "uniform paths" в основном спрятаны под feature, можно использовать только те которые начинаются либо с 1) имени другого крейта, либо 2) crate/self/super.
foo в примере выше ни то ни другое.

В текущем (1.31) поведении на самом деле тоже есть своя логика — если любой импорт начинается либо с имени крейта, либо с ключевого слова, то всегда сразу видно где импортируется своё, а где чужое.
Есть некоторые сторонники того, чтобы оставить всё как есть и "uniform paths" не включать.

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


Обожаю такие фразы. Звучит весомо, не значит ничего.

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

С этим что-то изменилось, или по-прежнему — либо бубен, либо под embedded понимается Raspberry «четыре ядра, четыре гига» Pi?
Так это годовой давности статья, большая часть проблем уже не актуальна.
И про это можно прочитать где-то в объёме большем, нежели «наши доблестные разработчики всё исправили»?
"Для встроенной разработки необходимо было повысить стабильность существующей функциональности."
Обожаю такие фразы. Звучит весомо, не значит ничего.

Почему ничего? Есть всякие нестабильные флаги и возможности компилятора, очень полезные для разработки под встройку, но еще не доступные в стабильном канале. Где-то в Embedded WG ресурсах список был — потихоньку дело толкают к стабилизации всего этого хозяйства.


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

А no_std бинари много места занимают? Вроде не должны бы.

А no_std бинари много места занимают? Вроде не должны бы.


Судя по регулярным вопросам на форумах, «случаи бывают разные», и внезапный рост бинарника в несколько раз от того, что в очередной ночной сборке что-то поломали или поменяли — бывает. А мы говорим о мире, в котором Clang/LLVM не используют, потому что у него до сих пор оптимизация плохая — и он процентов десять GCC проигрывает.

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

«внезапный рост бинарника в несколько раз от того, что в очередной ночной сборке что-то поломали или поменяли — бывает»


… необходимо было повысить стабильность ....


«Звучит весомо, не значит ничего».


Кхм, какие-то взаимоисключающие параграфы™.

И как, повысили? И про это можно прочитать где-то в объёме большем, нежели «наши доблестные разработчики всё исправили»?

Кхм, какие-то взаимоисключающие параграфы™.


Почитайте корпоративные пресс-релизы, они целиком из таких конструкций составлены.

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


… всё это оставляет ощущение глубокой альфы, на которую ещё несколько лет можно даже не смотреть.

С этим особо и спорить не стану — ржавая встройка в текущий момент это и правда еще удел энтузиастов и экспериментаторов, которые пытаются развить экосистему и довести до ума инструментарий. До серьезного "взял и в продакшн" тут еще море работы.


И как, повысили? И про это можно прочитать где-то в объёме большем, нежели «наши доблестные разработчики всё исправили»?

Можно, но за подробностями это с уровня таких больших "корпоративных релизов" надо спускаться на уровень конкретных Embedded WG ежемесячников (например, вот) или даже конкретных задач на гитхабе (например вот или вот).

Да, с edition = "2018" в Cargo.toml NLL включается.

Sign up to leave a comment.

Articles