Комментарии 19
use std::ptr;
fn main(){
let p: *const i32 = ptr::null();
println!("{:?}", p);
}
Мне кажется, вы не очень далеко прочитали про Rust.
Примитивный (базовый) тип pointer может иметь и null, и прочие гадости.
https://doc.rust-lang.org/std/primitive.pointer.html
Все остальные благородные типы (вроде slice или box) внутри имеют эти самые pointer'ы.
Только этот код не упадет, потому что разыменования не происходит, и указатель мало отличается от обычного числа. А разыменование придется оборачивать в unsafe-блок, то есть вручную дать гарантии отсутствия нулевого указателя.
Безусловно, это так. Разименование 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 периодически.
Получается, вместо постоянных проверок на NULL в Си, мы получаем постоянные поверки, есть ли что то в Option? Мне кажется, или это одно и то же.
Не совсем. В Си любой указатель может быть действительным, некорректным или NULL. В Rust только Option может быть либо None, либо чем-то (Some). Использовать Option в виде объекта не получится — компилятор не позволит. Если будете вытаскивать из Option, то компилятор заставит сделать это правильно. Если уже вытащили из Option, то больше проверять не нужно — это не будет None. И не может быть некорректных ссылок.
Используйте Option там, где может быть NULL, но там где NULL не может быть никогда, используйте указатели и не делайте никаких проверок. Код станет чище и понятнее.
youtu.be/cdX8r3ZSzN4
С лёгким налётом ржавчины или куда делся NULL