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

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

Замечание в целом верное, пока ещё читаю. :) Полагаю, что для связи с кодом на Си (в котором активно используется NULL) приходится выкручиваться через сырые указатели std::ptr. Для самого Rust такой концепции как NULL нет. А вот возможность есть. И это хорошо.

Примитивный (базовый) тип pointer может иметь и null, и прочие гадости.


https://doc.rust-lang.org/std/primitive.pointer.html


Все остальные благородные типы (вроде slice или box) внутри имеют эти самые pointer'ы.

Только этот код не упадет, потому что разыменования не происходит, и указатель мало отличается от обычного числа. А разыменование придется оборачивать в unsafe-блок, то есть вручную дать гарантии отсутствия нулевого указателя.


https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=6e21d9c0d908030bc1a9440f5a5146aa

Безусловно, это так. Разименование ptr — это unsafe, а часто и прямое UB (в зависимости от степени ужаса в указателе). Я привёл этот код для того, чтобы показать, что в Rust есть и указатели, и NULL.


Дальше нужно обсуждать, что такого волшебного было сделано в стандартной библиотеке, языке и системе типов, чтобы можно было мирно порхать по result'ам и box'ам ни разу не наткнувшись на луркающее снизу UB.

НЛО прилетело и опубликовало эту надпись здесь

У меня как раз появлось ощущение, что в интернетах образовался особый пласт Rust-о-фанов, которые Rust толком не знают, зато несут особый свет особого неофитства о том, как Rust, волшебным взмахом safe делает всё safe и какой он safe, и что в нём нет указателей и NULL, и не может быть ub.


Хотя на самом деле, Rust — это такая специальная дисциплина ума, плюс разумные дефолты, выписанные в виде языка, которые дают возможность меньше думать про тлен и больше думать про сложное. В отсутствие дисциплины код на Rust'е превращается в такую же кашу, как и на любом другом языке.

НЛО прилетело и опубликовало эту надпись здесь

Я не чуть не собираюсь оправдывать С++; более того, я очень люблю Rust. Но!


safe в rust даёт достаточно ограниченные гарантии. Я сейчас попытался найти тот пример, которым мне ткнули недавно, где сделали реальный wtf UB в совершенно safe-коде всего лишь с комбинацией двух lifetime'ов и одного static'а (на чтение). Увы, не нашёл.


Более того, многие не понимают, что от чего именно safe Rust. Там всего лишь хотят сделать так, чтобы не было UB. В ходе этого "всего лишь" оказывается, что очень много "edge case'ов" других языков — нифига не 'safe' (вставление в set элемента при итерации по нему, например).


А вот вторая часть Rust, которую почему-то не замечают, на самом деле, значит куда больше, чем война с UB (под словом safe). Например, очень, очень, разумные дефолты. Copy для непримитивных типов только в явном виде (move по-умолчанию), ничего не public пока не сказано обратное, всё readonly пока не попросили mut, никаких type elision в неожиданных местах (foo as usize 100500 раз), запрет на модификацию чужих трейтов для чужих типов.


Я бы сказал, что вот эта часть (разумные строгие дефолты) значит куда больше, чем культ вокруг safe. Во многих языках unsafe (в контексте raw pointers) просто нет, и они себя отлично чувствуют, т.е. само по себе "скрытие" ptr — это не особое достижение.

Вот пример, воспроизводящий упомянутую вами проблему (использование после освобождения):


static R: &&i32 = &&0;

fn aux<'a, 'b>(_: &'a &'b i32, arg: &'b i32) -> &'a i32 {
    arg
}

fn foo() -> &'static i32 {
    let a = 0;
    let bar: fn(_, &i32) -> &'static i32 = aux;
    bar(R, &a)
}

fn main() {
    let res = foo();
    println!("{}", *res);
}

Запустить


И это — баг в компиляторе: https://github.com/rust-lang/rust/issues/25860

Все-таки данная заметка — не про UB, а про неудобства с использованием null и "падения" программ из-за него. Думаю здесь уместнее было бы сравнение с Java (или с другим языком, имеющим null), а не с указателями C/C++.

Да, в контексте сравнения null джавы (который не ведёт к UB, а просто к runtime ошибкам) — у раста есть некоторые преимущества, но..


let a = foo().unwrap()

или экивалент jav'овый без проверки на null на самом деле одно и то же. Вернули None/Null — упади. Т.е. компилятор подразумевает обработку ошибок, но вовсе её не требует. В этом смысле, кстати, [] в rust ведёт себя так же, как и java-код. Вылетел за границы — вот тебе паника.

Т.е. компилятор подразумевает обработку ошибок, но вовсе её не требует.

Ну как же? Очень даже требует, иначе не пришлось бы явно писать unwrap. Не требует "обрабатывать правильно" — да, но в общем случае это невозможно (где-то и падать на панике — вполне нормальное поведение).


С нулабельными типами проблема в том, что нельзя статически гарантировать наличие значения, вот и вылетают NullPointerException периодически.

а, почитал, что в java. У них всё может быть null, и невозможно сконструировать non-nullable тип. Бедные, бедные джависты.

Получается, вместо постоянных проверок на NULL в Си, мы получаем постоянные поверки, есть ли что то в Option? Мне кажется, или это одно и то же.

Не совсем. В Си любой указатель может быть действительным, некорректным или NULL. В Rust только Option может быть либо None, либо чем-то (Some). Использовать Option в виде объекта не получится — компилятор не позволит. Если будете вытаскивать из Option, то компилятор заставит сделать это правильно. Если уже вытащили из Option, то больше проверять не нужно — это не будет None. И не может быть некорректных ссылок.

Да, разница только в обязательности. На C/C++ вы можете говнокодить как вам вздумается, а компилятор Rust вам этого не даст.
Option как-раз позволяет избежать лишних проверок там, где они не нужны.
Используйте Option там, где может быть NULL, но там где NULL не может быть никогда, используйте указатели и не делайте никаких проверок. Код станет чище и понятнее.
Открыть эту тему (уже открыто), вторым окном открыть ютюб — в рекомендациях будет это:
youtu.be/cdX8r3ZSzN4
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории