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

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

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

Есть там ещё к чему придраться, есть. Нет стабильных целочисленных параметров дженериков, из-за чего массивы не являются первоклассными типами. Нет стабильного синтаксиса для создания макросов и для боксинга. Нет стабильного placement new.


Нет простых экстеншенов, а существующий обходной путь не дружит с возвращаемым impl Trait, и нет стабильного способа их подружить.

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

А зачем вам в Rust с его дешёвым перемещением placement new?

Лично мне — просто для порядка. Лишнее перемещение всё равно явно лишнее, даже если дешёвое.

У placement new в C++ прикол вроде больше с тем что можно избавится от выделения динамической памяти, насколько я это понимаю.

Sorry за медленный вопрос, а можете продемонстрировать как это сделать?


trait Trait {}

struct Hello {
   something: Box<dyn Trait>
}

Как можно положить в Hello.something что-то что реализует Trait, но без динамической памяти?

В таком виде — никак, ведь Box подразумевает динамическую память. В плюсах, знаете ли, тоже placement new для unique_ptr не работает!


А вот так динамическая память уже не обязательна:


struct Hello {
   something: &dyn Trait
}

Спасибо за ответ! Я имел в виду могу ли я заменить чем-то Box чтобы хранить объект в рамках Hello. В плюсах я могу вместо unique_ptr сделать:


struct ITrait {};
struct Hello {
    uint8_t something[MAX_SIZE];
}

И размещать в этом буфере объекты с помощью placement new и использовать с помощью `(ITrait*)(something)->doSomething'. Если объект Hello размещен на стеке, то объект производного класса тоже будет на стеке и динамический полиморфизм будет работать.


В вашем варианте не хватает лайфтаймов и подразумевается что Hello не владеет объектом (в отличие от Box). Весь мой опыт с rust что не хватает многих таких мелочей которые можно сделать оптимальнее в C++, но приходится делать немного менее оптимальнее (за счет рантайма).

Кстати, так, как вы написали, работать, вообще говоря, не будет: 1) нужно обеспечить выравнивание указателя, 2) даже если с выравниванием всё хорошо, нельзя использовать указатель, сконверченный из uint8_t, это UB; надо использовать указатель, который вернул placement new.

Я согласен, но это довольно просто решаемые вопросы.

Ага, а если объект вышел за пределы MAX_SIZE — получите и распишитесь повреждение памяти. Если плюсы такое позволяют — это ещё не значит что так и правда можно делать.

В этом сценарии placement new нужно понимать некоторое количество деталей и это не rocket since. Как много где в C++ надо проверять возможные переполнения и тут это можно делать в compile time (так что программа не соберется если вы пытаетесь сделать placement new для объекта который размером больше MaxSize). В реальности это будет не uint8_t something[MAX_SIZE]; а что-то вроде InplaceStorage<ITrait, MAX_SIZE> storage_; где эти вопросы решены один раз.


В rust наверное все это тоже можно как-то сделать с MaybeUninit и Pin, возможно не так часто нужно.

В rust наверное все это тоже можно как-то сделать с MaybeUninit и Pin

Хотелось бы увидеть. Я вот не знаю как это сделать.

+1. Я вот не понимаю этот момент, раст говорит что async/await у него это zero cost, однако это явно не zero cost поскольку там сразу несколько костов, в том числе на выделение памяти. В корутинах у C++ есть сценарии когда выделение памяти может быть убрано компилятором и кроме того там можно своим "new" управлять выделением памяти для корутин.

Ну кстати в раст async/await не требует выделения динамической памяти (отвечаю себе спустя год, вышел на эту страничку поиском, увидел свои комментарии, увидел что я что-то не знал тогда).

Нет стабильного placement new.

Нестабильного тоже уже нет, выпилили

Ещё я вспомнил костыль под названием FnBox.


И лично я бы ещё с большим удовольствием увидел условные заимствования. Чтобы например можно было делать так:


fn fill_buf_and_ignore_interrupts(reader: &mut impl BufRead) -> io::Result<&[u8]> {
    loop {
        match reader.fill_buf() {
            Ok(buf) => return Ok(buf),
            Err(e) => {
                if e.kind() != io::ErrorKind::Interrupted {
                    return Err(e);
                }
            }
        }
    }
}

Сейчас borrow checker такое осилить не в состоянии.


Ну и оптимизация хвостовой рекурсии не помешала бы, так как замена цикла рекурсией в некоторых случаях позволяет лучше объяснить borrow checker'у что к чему.

Ещё я вспомнил костыль под названием FnBox

Уже раскостылили, теперь можно использовать Box<dyn FnOnce()> безо всяких костылей
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории