Skip to content

Instantly share code, notes, and snippets.

@kpp
Last active February 26, 2018 14:24
Show Gist options
  • Save kpp/d3779f4eac144b665475512d11f9d1cf to your computer and use it in GitHub Desktop.
Save kpp/d3779f4eac144b665475512d11f9d1cf to your computer and use it in GitHub Desktop.
habr article

Эта статья - квинтэссенция срача.

Q. Безопасен ли Rust? Можно ли его использовать в проде?

A. Институт программных систем Общества Макса Планка плотно занимается этой проблемой в проекте под названием RustBelt. На январь 2018 формально(sic!) доказаны следующие постулаты:

  • Система типов, принципы владения (ownership), времени жизни (lifetime) корректны
  • Программа безопасна, если все участки кода внутри unsafe безопасны
  • Доказаны безопасность и корректность реализации некоторых участков библиотек, активно использующих unsafe: Arc, Rc, Cell, RefCell, Mutex, RwLock, mem::swap, thread::spawn, rayon::join and take_mut(which one???)

Интересный факт: во время доказательства корректности был найден баг в MutexGuard. К счастью, фикс в 3 строчки не заставил себя ждать.

Yay for formal methods :D

            Ralf Jung 

Q. А можно как проходящему мимо человеку запросить более обстоятельный комментарий (а возможно даже и статью) про разницу в философии проектирования программ?

A. Rust — уникальный проект. И как язык и как экосистема.

Как язык он уникален тем, что ядро команды составляют великолепные специалисты по теории типов, которые, тем не менее, ориентируются на практичность, а не на академическую чистоту/новизну языка. В итоге получился язык с весьма мощной системой типов, который однако, можно успешно применять в повседневной разработке.

Философия языка — это сплав современных представлений computer science и хиропрактик системного программирования, повернутых, однако, лицом к программисту-прикладнику.

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

Да, тут я говорю и про C++. Программирование на C++ — это сделка с дьяволом. Ты получаешь большие возможности в обмен на свою душу, когда самолет под управлением твоей программы грохнется в океан. C++ пестрит ситуациями, приводящими к неопределенному поведению, о которых программист обязан помнить.

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

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

Ключ к решению — формализация тех отношений в коде, которые в традиционных языках остаются на совести программиста. Например отношения владения памятью и поддержания ссылочной целостности на уровне указателей. Rust физически не допускает ситуаций, могущих привести к неопределенному поведению, нарушению алиасинга, состоянию гонок или обращению к уже освобожденной или еще не выделенной памяти.

Все вместе позволяет без опаски реализовывать (а главное рефакторить!), сложные многопоточные программы. Здесь очень кстати будет классический уже пост Fearless Concurrency от одного из авторов языка, плюс великолепная серия статей Lin Clark про внутреннее устройство Firefox Quantum, который стал возможен исключительно благодаря возможностям Rust.

Как экосистема Rust уникален тем, что ядру команды удалось сформировать удивительное сообщество, готовое к диалогу и дружелюбное к новичкам. Буквально каждый момент взаимодействия с этим сообществом вызывает чувство глубокой благодарности. Тем удивительнее это видеть в среде, близкой к системному программированию.

Q. А в каких случаях страдает производительность, что вынуждает использовать unsafe?

A. Тут сразу стоит сказать что сам по себе unsafe не делает ничего особенного и не увеличивает магическим образом производительность на 146% самим фактом своего использования. Он всего лишь позволяет программисту сказать компилятору «я знаю что делаю» и успокоить его анализатор, который в противном случае бы не дал написать потенциально небезопасную конструкцию.

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

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

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

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

Один из примеров, где такое срезание углов имеет смысл — это реализация итератора по массиву. Доступный размер массива известен заранее, поэтому итератору достаточно хранить у себя текущий индекс. При создании итератора текущий индекс выставляется в 0 и увеличивается на единицу каждый раз, когда пользовательский код зовет next и если в массиве есть еще непосещенные элементы. Поскольку этот индекс по определению не может выйти за пределы массива, имеет смысл операцию обращения к элементу делать без проверки индекса (проверка заложена в саму логику итератора), тем самым, повышая производительность, путем убирания лишней проверки, а не всех проверок вообще. Код по-прежнему остается безопасным, просто обеспечение этой безопасности вынесено на другой уровень.

По этой самой причине Rust рекомендует писать код именно с использованием итераторов и фильтров, так как они оказываются более эффективными, нежели традиционный императивный код с доступом по индексу.

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

Q. А можно примеры с unsafe?

A. Например, Vec::get_unchecked. Весь метод помечен как unsafe, т.е. за корректность индекса при вызове этого метода вы отвечаете сами.

fn main() {
    let x = &[1, 2, 4];
    
    unsafe {
        println!("x[1] = {}", x.get_unchecked(1)); // 2
    }
}

Run example

Q. Объясните, что такое str (не &str!) с точки зрения программы?

A. str — это безразмерный (unsized) тип, представляющий строковое значение, а &str — ссылка на него. Примерно как void и void* в Си, только без костылей.

Q. Как достигается отсутствие гонок данных?

A. С помощью трейтов Send и Sync.

Q.

A.

Q.

A.

Q.

A.

Q.

A.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment