Новые эзотерические возможности Раста

@ozkriff скинул в чате, и я нашёл в этой теме несколько интересных трюков.

https://www.reddit.com/r/rust/comments/8u86uc/what_are_the_most_recent_esoteric_rust_features/

  • tuple struct constructors (Type(a, b, c) or Enum::Variant(a,b,c)) can be passed as function pointers

    (0…10).map(Some)

  • This isn’t really esoteric, but apparently it’s existed for a while and I had no idea that it was a thing: you can put defaults in generic type definitions:

    pub type MyResult<T = ()> = Result<T, MyError>;

    So now you can use MyResult as the return value of a function, instead of having to write MyResult<()> everywhere. And if you are returning, say, u32 then you can still write MyResult.

  • Rust has a drop operator, which can be useful for dropping runtime borrows: ;. For example:

    let c = RefCell::new(5);
    let b = c.borrow();
    b;
    let b = c.borrow_mut();

  • A nested function definition can act like a safe code block within an unsafe function. When writing unsafe code, I try to minimize the code in the unsafe block/function to only the absolutely necessary bits. I’ll often write a safe version of the unsafe function that performs the desired logic, which is then called by the unsafe function. This can get messy though, and the direct association of the unsafe/safe functions might not always be obvious to the reader.

    A way to more directly communicate that the safe code only exists for the unsafe function is to declare it as a function inside the unsafe function. E.g.,

    unsafe fn do_my_things(arg: UnsafeType) → Output {
    fn logic(ensafenated_arg: Type) → PreOutput {
    // Attempting to do anything unsafe here will fail during compilation
    }

    let ensafenated_arg = unsafe_transform(arg);
    let pre_output = logic(ensafenated_arg);
    final_unsafe_transform(pre_output)
    

    }

2 лайка

Вроде бы, ничего из процитированных трюков не новое, а вполне себе давно доступно было для желающих)

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

Rust has a drop operator

И он, внезапно, так и зовётся: drop(). Однако люди зачем-то выдумывают костыли вроде foo ; или let _ = foo;.

1 лайк

Метод std::ops::Drop::drop() нельзя вызвать явно - ошибка E0040. Нормально он вызывается неявно при выходе переменной из области видимости.
Еще есть функция std::mem::drop(), которая вызывает вышеупомянутый метод drop() для своего аргумента.
Такие дела :wink:

Он про mem::drop функцию и писал жеж.

Но я в целом не понял серьезности комментария - в ссылке-то скорее набор шуточных трюков и “смотрите, как, оказывается, можно делать”, а не “вот так в продакшн фигачить надо”-советов. :slight_smile:

Собственно, дроп есть в прелюдии, так что можно даже не импортировать std::mem и писать просто drop(foo).

Понятно, что про точку с запятой - это шутка юмора. Просто уточнить хотел что drop не оператор, хотя его типаж в разделе ops. Он сам по себе вещь эзотерическая.

А поясните, в чем разница между

let b = c.borrow();
b;
let b = c.borrow_mut();

и

let b = c.borrow();
let b = c.borrow_mut();

?

Второе не скомпилируется.

https://doc.rust-lang.org/std/cell/struct.RefCell.html#method.borrow_mut

^ скомпилируется, но упадет же во время выполнения с паникой:

https://play.rust-lang.org/?gist=1214484a306c9af3958ca5b14a78cb9c&version=stable&mode=debug&edition=2015

Да, я имел ввиду “не скомпилировалось бы в случае статического заимствования”…

Ну то, что второе падает - я и сам знаю. Я не пойму, почему (в свете того, что первое работает)?

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

use std::cell::RefCell;

fn main() {
    let c = RefCell::new(5);
    {
        let borrowed_five = c.borrow(); // живет только в этих скобках
    }
    let borrowed_five2 = c.borrow_mut();
}

Так что к моменту вызова borrow_mut, никаких живых ссылок уже нет и конфликтов не возникает.

То есть

let b = c.borrow();
b;
let b = c.borrow_mut();

По семантике тоже, что и

{
    let b = c.borrow();
}
let b = c.borrow_mut();

? Как это так?

в строчке b; происходит поглощение переменной b, как если бы мы вызвали drop(b); - ровно то же, что с этой перемнной случается во варианте со скобками при достижении }.

1 лайк

Вот это-то и не очевидно… Надо привыкнуть к такому повороту :slight_smile:

1 лайк

Да забей, все равно компилятор на ткое предупреждение выдает:

warning: path statement with no effect

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

Однако, эффект-то вполне себе! :smile:

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

2 лайка

Хорошо, что есть предупреждение. А то, такую магию не хотелось бы иметь в коде :grinning: