Комментарии 399
собралось == работает
для написания абсолютно любых приложений
которые не задумываются о производительностиПризнайтесь, вы специально? )
Не знаю, в чем тут наброс. Я действительно считаю, что раст форсится исключительно как "Better C", который "такой же быстрый, но безопасный". И совершенно игнорируют тот факт, что это хороший язык общего назначения, на котором и микросервисы серверлесные можно делать, и другие интересные штуки. Все знают, что крутую распределенную систему можно быстро и удобно сделать на Akka, а вот что есть точно такой же фреймворк для раста (actix
), в котором точно так же удобно можно всё сделать на расте — уже нет.
Я правда считаю, что раст продуктивный, и что его производительность — это хорошая, но далеко не единственная черта.И во многих случаях ей можно пожертвовать, получим очень простой и изящный код. В случае с тем же ботом у меня ~3-4 сетевых запроса на каждый пост пользователя: сначала обработка самого сообщения, затем запрос урлов файлов, затем запрос контента файлов, и потом еще и отправка сообщения в телеграм. Сколько относительно этого ест клонирование единственной строковой переменной?
С появлением async/await (уже есть в ночнике, я бота его недавно на него переписал) должна отойти самая насущная на сегодняшний день проблема, неудобный async IO на коллбеках. А других серьезных проблем, способных помешать продуктивно писать код, я не вижу. После некоторой практики на расте можно писать с той же скоростью, что и на C#, но получать намного более серьезные гарантии корректности. Да, эту практику нужно набить, но как я уже сказал в статье, "тяжело в учении — легко в бою", вы учите концепцию один раз, и дальше это как езда на велосипеде, всегда с вами до конца жизни. Затратили сколько-то времени, дальше этим пользуетесь, и чем раньше изучили, тем больше времени сэкономили.
Сам давно хотел попробовать что-нибудь кроме .NET на бекэнде. Но после EntityFramework как-то плеваться начинаешь на ORM-ы в других языках.
Прям уровень EF это пока рановато, не зря он уже по сути восьмой версии (6 версий взрослого фреймворка, и две на Core). Но в целом, работать с БД можно.
Спасибо, почитал! Я так понял, что концепта UnitOfWork
там нет?
Хотя вот это — огонь :)
// Using `include_str!` allows us to keep the SQL in a
// separate file, where our editor can give us SQL specific
// syntax highlighting.
sql_query(include_str!("complex_users_by_organization.sql"))
А аналог nameof()
в Расте есть?
Есть макрос stringify!()
, превращающий токены в строку. И есть крейт nameof
на его основе, делающий примерно то же, что и nameof()
.
Спасибо, почитал! Я так понял, что концепта UnitOfWork там нет?
Ну вроде как let connection = pool.get();
это оно, нет?
А аналог nameof() в Расте есть?
нет, но мы же про раст говорим :) 2 секунды, и уже есть :)
macro_rules! name_of {
($x:ident) => {
stringify!($x)
}
}
fn main() {
let my_variable = 10;
let name = name_of!(my_variable);
println!("{}", name);
}
let connection = pool.get(); это оно, нет?
Я имею в виду аналог DbContext:
post.published = true;
post.save_changes(&connection);
Если я правильно понимаю, save_changes(&connection)
сохраняет прямо в базу. А группировка изменений по нескольким сущностям обеспечивается явной транзакцией. В противовес DbContext.saveChanges() из EF, который группирует изменения в один запрос.
name_of!(my_variable);
А name_of(Post::published)
можно? В .NET я это использую для того, чтобы RAW SQL не требовал изменений после рефакторинга названий классов и полей.
А name_of(Post::published) можно? В .NET я это использую для того, чтобы RAW SQL не требовал изменений после рефакторинга названий классов и полей.
Ниже сказали, я немного неверно понимал как $ident работает. Нужно подумать еще.
> А name_of(Post::published) можно?
можно всё, что является идентификатором. Можно посмотреть на реальный макрос реального крейта, там из комментов понятно, какие случаи работают, и даже как именно их обрабатывать: docs.rs/nameof/1.0.1/src/nameof/lib.rs.html#72-93
Увы эта реализация не похожа на nameof
из C#
macro_rules! name_of {
($x:ident) => {
stringify!($x)
}
}
fn main() {
let my_variable = 10;
let name = name_of!(my_variable_1);
println!("{}", name);
}
выведет в консоль
my_variable_1
using System;
public class Program
{
public static void Main()
{
var myVariable = 10;
Console.WriteLine(nameof(myVariable1));
}
}
не скомпилируется с ошибкой:
Compilation error (line 8, col 28): The name 'myVariable1' does not exist in the current context
Compilation error (line 7, col 7): The variable 'myVariable' is assigned but its value is never used
Да, вы правы. Я забыл, что $ident может не только использовать существующий идентификатор, но и новый объявлять. Беру паузу на размышление :)
Подглядел в крейт nameof, чтобы посмотреть, как они это сделали. Собственно, так же, как и я, только с гвардом, который как-то использует переменную: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=40fdb6ff604b9f094faaaaf40ee826cb
можно всё, что является идентификатором. Можно посмотреть на реальный макрос реального крейта, там из комментов понятно, какие случаи работают, и даже как именно их обрабатывать: docs.rs/nameof/1.0.1/src/nameof/lib.rs.html#72-93Код великолепен — как древние С- макросы, только с привкусом брейнфака.
Макросы и трейты — зло. Макросы из трейтов — зло в квадрате.
Мы кажется, пытались в топике говорить о надежности языка и о прикрытии пятой точки компилятором — так твой пример показал, что нет — здесь (в таком вот использовании) _дыра_.
И я говорю только о том, что это так и в других языках с похожими возможностями — может с разной степени последствий.
И нет, писать на LLVM коде я не планирую — как и ассемблер — он небезопасен в использовании. Хороший язык должен максимально обходиться своими средствами, без всяких там asm/llvm вставок и трейтов тоже. А макросы просто устарели, типобезопасные шаблоны(генерики) — на две головы впереди.
Надо жить без синтаксических макросов. 3й раз пишу, не доходит.
Кому надо? Зачем надо?
Сейчас заканчиваю статью о надежном программировании — вот велкам там прогнать раст через прокрустово ложе всех требований (потому что я этого сделать все= не смогу).
Всем надо. Граблеопасная техника.
Так «в чем опасность-то»? Или это «очевидно» и в обосновании не нуждается?
Вообще то после некоторого опыта конечно очевидно. Ослабляет проверку типов.
Но еще есть и гугл и учебники. В частности, в Misra C 2004 правило 93 как рекомендация не использовать, и в Misra C++ 2008 правило 6-2-2 вообще запрещающее функциональные макросы.
А сосед-растишка — говорил обратное. Поскольку я не знаю, кому верить, закругляюсь.
Там ничего не написано про "макросы в расте такие же как в С". И я предложил бы вам вежливее отзываться о ваших коллегах-программистах.
Но поскольку ошибка в наличии, она таки требует объяснений.
Ошибка в том, что я забыл, что $ident
должен являться валидным идентификатором. Но в момент вызова макроса мы этот идентификатор и создаем. Этим можно воспользоваться, например, для генерации бойлерплейт-кода:
macro_rules! construct_uint {
($name:ident, $n_words:tt) => (
#[repr(C)]
#[derive(Copy, Clone, Eq, PartialEq, Hash)]
pub struct $name(pub [u64; $n_words]);
impl $name {
pub const MAX: $name = $name([u64::max_value(); $n_words]);
}
)
}
construct_uint!(U128, 2);
construct_uint!(U256, 4);
construct_uint!(U512, 8);
Поэтому мой код проверял существование идентификатора, но он всегда существует, т.к. мы его и создали, вызывав макрос (как в примере выше).
Фикс: попробовать понять, что скрывается за этим идентификатором: переменная, тип, метод или еще что-нибудь. Если это не получится сделать, значит идентификатор ни к чему не привязан, и это его единственное использование.
Насчет дыры, это не большая дыра, чем такой код
fn add(a: i32, b: i32) -> i32 { a - b }
Раст позволит объявить функцию сложения, которая на самом деле вычитает. И ничего не скажет даже.
Я честно перебрал термины — пишет на С — сишник, на паскале — паскалист, на расте = ??? растер, растишник. Готов к предложениям.
Действительно, это больше похоже не на синтаксический макрос, как утверждалось выше «соседом», а на шаблон(генерик) с использованием трейтов компилятора — что совсем другое дело.
Тем не менее, я внимательно посмотрел код и вижу (кроме отсутствия безопасности в работе с типами) еще один великолепный пример гениального синтаксиса, требующего 4х кратного копипаста (для ссылок, констант итп — хз).
Потом еще глаз зацепился за unsafe в Особо Опасной функции вывода в строку.
На этом мои изыскания по расту окончены. Спс за общение.
P.S Код по ссылке выше, — можно вполне брать за образец и переписывать под свой язык — написан симпатично — это библиотечка работы с целыми числами любой разрядности
А чем не нравится «растишка» (без негатива)?
Я честно перебрал термины — пишет на С — сишник, на паскале — паскалист, на расте = ??? растер, растишник. Готов к предложениям.
Английский вариант — rustacean, русский, видимо, растовчанин.
Тем не менее, я внимательно посмотрел код и вижу (кроме отсутствия безопасности в работе с типами) еще один великолепный пример гениального синтаксиса, требующего 4х кратного копипаста (для ссылок, констант итп — хз).
Можно подробьнее, в чем копипаст.
Потом еще глаз зацепился за unsafe в Особо Опасной функции вывода в строку.
Спасибо, эту функцию я и писал. unsafe
там, потому что для функции предполагается работать в no_std
формате, без аллокаций памяти. Соответственно, в no_std
нет стандартных классов строк/векторов (они все завязаны на аллокации в куче), поэтому вот так.
И да, unsafe это вещь, которой можно пользоваться. Если код помечен этим словом, это не означает, что тут ужос ужос и надо всё выкидывать.
-ишк
Словообразовательная единица (суффикс)
1. под ударением при добавлении к основе существительного образует существительное со значением пренебрежительности
— https://ru.wiktionary.org/wiki/-ишк
Текстовая подстановка — макросы
Макросы не занимаются текстовой подстановкой, если только это не С.
Мы кажется, пытались в топике говорить о надежности языка и о прикрытии пятой точки компилятором — так твой пример показал, что нет — здесь (в таком вот использовании) _дыра_.
И я говорю только о том, что это так и в других языках с похожими возможностями — может с разной степени последствий.
Если бы я писал реальный код, я бы написал два теста: на успешное и неуспешное прохождение, и отловил бы ошибку. Да, от вообще всех ошибок раст не спасает.
И нет, писать на LLVM коде я не планирую — как и ассемблер — он небезопасен в использовании. Хороший язык должен максимально обходиться своими средствами, без всяких там asm/llvm вставок и трейтов тоже.
То есть все языки, собирающиеся в LLVM стали дырявыми?
А макросы просто устарели, типобезопасные шаблоны(генерики) — на две головы впереди.
Кажется, вы не знаете, что такое макрос.
Нужны таки еще юниттесты.
это прогресс в признании ошибочных заявлений.
А можно увидеть это заявление? А то пока походит больше на strawmen аргументацию.
Остальные пункты вы, видимо, решили проигнорировать.
Уверенность в том, что собралось == работает действительно имеет место. Это не значит, что в коде нет багов, это значит, что все баги связаны с логикой приложения.не считая комментариев
это что касается успешной компиляции с несуществующей переменной
про остальное я не вижу конкретных вопросов. про макросы — ваш сочувствующий съехал на «синтаксическую составляющую», например, и что это нормально :fail:
ладно. я с одной стороной думаю, что данный пример показательный «как не надо делать» и в обычном использовании редок, но с другой — списки…
про макросы — ваш сочувствующий съехал на «синтаксическую составляющую», например, и что это нормально
Конечно. Макрос принимает просто токены. Если вы эти токены внутри макроса игнорируете, как элементы синтаксиса, то никакой ошибки компиляции не будет и быть не должно.
Но тебе большой вопрос — понимаешь ли ты разницу и можешь ли сказать однозначно — макросы в расте это просто синтаксическая подстановка (как заявлялось ранее) или компилируемый шаблон (который с проверкой типов)?
Потому что своим недопониманием (или недостаточным скиллом объяснений) ты подставляешь всё раст-сообщество как класс.
Все, что передается макросу на вход — это синтаксис, дерево токенов. Семантический смысл он обретает только в раскрытии макроса. Поэтому, если переданный синтаксис в раскрытии не используется, то компилятору совершенно фиолетово, что он из себя представляет по смыслу:
macro_rules! foo {
($($t:tt)*) => {};
}
fn main() {
foo!(Fuck your semantic!);
}
Ни один из переданных токенов не используется в раскрытии макроса как существующий идентификатор. Поэтому не будет ошибкой передавать несуществующие идентификаторы в макрос (или вообще любые токены, как в приведенном выше примере). И если вы себе в голове придумали, что передаваемый идентификатор должен быть существующим до макроса, но в раскрытии этого не отразили, то совершенно непонятно, почему компилятор должен уметь читать ваши мысли и информировать об ошибке в этом случае.
И совершенно игнорируют тот факт, что это хороший язык общего назначения, на котором и микросервисы серверлесные можно делать, и другие интересные штуки
Все упирается в то что крейтов или нет вообще, или заброшенные, или несовместимые.
Я вот недавно писал мелкий сервис который болтает по GRPC, конвертит картинки в jpg/webp, кропает, и заливает в google cloud storage. Три дня на го.
И я хотел бы написать то же на расте, но с картинками как-то все сложно, с API к GCS тоже, ну вот и все — интерес кончился, надо чтоб работало.
С этим трудно поспорить. Я полгода потратил на написание cv-rs
(биниднги к opencv), чтобы мой бот мог картинки анализировать. Было бы намного проще, если бы это кто-то сделал до меня.
Но для обычного приложения в стиле MVC с DAL слоем уже всё есть. Для специфики могут быть свои нюансы.
grpc, webp, google cloud
Ну было бы странно, если бы на go не было всё готовое для использования этих технологий.
GRPC официально работает на C++, Java, Python, Go, Ruby, C#, Node.js, Objective-C, PHP, Dart и в броузере.
GCS API биндинги есть для C++, C#, Go, Java, Node.js, PHP, Python, Ruby.
Таким образом можно взять любой из 8 языков где код будет просто работать из коробки — вопрос конвертации картинок я опускаю, но в каждом из восьми есть либа котора умеет делать ресайз и сохранять в webp.
В rust есть набор костылей (объективно — тот же порт grpc не умеет несколько вещей которые мне нужны) и конструктор "сделай лего сам". Там где в PHP вы компонуете компоненты, хук, хук и впродакшен, на расте надо все еще писать обертки к API.
Я не говорю что раст плохой, я говорю что у них (в отличие от того же Go/Python) "batteries are not included", а в cargo разброд и шатание.
Там только акторы и всё. В акка есть кластер, стримы, http наконец и многое другое.
Этот фреймворк далеко не просто акторы, в нем много библиотек выстраивающих экосистему.
Все равно что сказать, Spring в Java это DI ))
Кластеризация это да, мощная штука, но как уже говорил парень с дотнекста «Я вот щас вам рассказал про кластеры, но если есть возможность, НЕ ДЕЛАЙТЕ их» :)
В данном случае, возможно, вы получите такой буст по производительности. что и кластер уже не нужен будет. И это снимет целую кучу потенциальных проблем.
Хотел этот вопрос задать в интернетах, но раз уж такая пляска, спрошу тут:
Вчера писал калькулятор на расте. Суть в том, что можно написать "2 + 2", он это отпарсит и вернет ответ. Я сделал это по привычке на ООП: для выбора действия, я сделал трейт Operation с единственным методом run(i32, i32) -> i32. Сделал структуры с этим трейтом (самы структуры получились пустыми, в них нет состояния) и положил их в словарь в виде трейт объектов, которые дергаются по требованию.
У меня, однако, есть подозрение что это не идиоматично для раста. Я думал использовать функции вместо трейта, но это будет работать лишь в этом частном случае, поскольку у операций нет состояния. Если нужно будет сохранять состояние все равно придется делать структуры, поскольку раст не поддерживает каррирование.
Ну, полагаю, rust-way более функциональный в данном случае. Там, где в ООП вы делаете абстрактный класс и кучу наследников, в ФП вы делаете один энум и матчите его в тех местах, где вам нужно. Где-то это дает выигрыш, где-то нет, это известная проблема выражения.. И для раста это выходит более естественно, чем прямой перенос ООП опыта. Я в серьезных крейтах трейт-объектов вообще не встречал, динамическая диспетчеризация используется очень редко.
Вот пример крейта, построенного достаточно идеоматично: https://github.com/z2oh/sexe/blob/master/sexe-expression/src/lib.rs
Там, где в ООП вы делаете абстрактный класс и кучу наследников, в ФП вы делаете один энум и матчите его в тех местах, где вам нужно
так это же по сути «смешать код в кучу» вместо инкапсуляции?
К комментарию выше я бы добавил, что есть два варианта: Вы заранее знаете всё множество операций, оно статично и не будет меняться по ходу дела (и тогда enum дёшево и надёжно решает задачу), либо Вы заранее не знаете, оно будет зависеть от пользователя Вашей библиотеки или ещё как-то динамически изменяться (тогда Ваш подход с трейт-объектами, хоть он и дороже, — лучше решает задачу).
К слову сказать, в Расте есть замыкания (которые "под сахаром" на самом деле тоже структуры).
У вашего варианта есть фатальный недостаток. Что если вам понадобятся унарные или тринарные операции?
В функциональных языках есть один очень популярный паттерн — интерпретатор. Реализуется он обычно либо при помощи tagless final кодирования выражений, либо при помощи GADT. GADT в Rust нету, а вот простенький tagless final мы можем сделать используя трейты.
Можно объявить трейт Expression
trait Expression {
fn add(&self, right: &Self) -> Self;
fn sub(&self, right: &Self) -> Self;
fn negate(&self) -> Self;
fn eq(&self, right: &Self) -> Bool;
}
И сделать для него конкретные реализации, каждая реализация будет конкретным интерпретатором. Например одна реализация будет возвращать строку для вывода, вторая считать результат выражения, третья просто дублировать значение для того, что-бы одно выражение превратить в два выражения, с разными типами.
Для расширения Expression
можно использовать "наследование" трейтов (хотя в большинстве случаев будет проще и лучше запихать операцию в изначальный трейт):
trait ExpressionMul : Expression {
fn mul(&self, right: &Self) -> Self;
}
Для удобства потом можно сделать функции, которые будут фиксировать тип в нужном месте.
fn eval(i: i64) -> i64 { i }
fn stringify(s: String) -> String { s }
fn double<E1: ExpressionMul, E2: ExpressionMul>(pair: (E1, E2)) -> (E1, E2) { pair }
Хорошая статья.
Правда я придерживаюсь мнения, что язык, скорее системный, и мне проще и в пяток раз быстрее делать сервисы на го, а в раст выносить криптографию, системные вещи. Иначе цена решения становится несоразмерной.
"Ну и могу сказать, что по прошествии года с того момента как я впервые начал на расте писать, я научился писать простые сниппеты без ошибок с первого раза."
Это очень ведь дорого...
Это очень ведь дорого...
То, что человек совершает ошибки — вполне нормально.
Раст — язык с дополнительным контролем корректности программы.
Компилятор берёт на себя себя проверку декларируемых намерений и реального их выполнения. То есть, человек гараздо чаще сталкивается с ними не "когда-то потом", а непосредственно на этапе компиляции.
С помощью комплиятора и линтера постепенно вырабатывается навык писать сразу правильно и в соответствии с хорошим стилем. То, на что на самом деле в других языках уходят годы практики и что зависит от культуры написания кода в команде, если программисту повезло оказаться в хорошей команде.
Использовать Rust как дополнительный язык для ускорения узких мест — на самом деле тоже хорошее применение и важная ниша, в которой практически нет конкуренции. Да и глупо рассчитывать на то, что все разом забросят языки, изучению которых посвятили годы, и внезапно займутся переписыванием работающих проектов на новый язык с нуля (что может быть просто экономически нецелесообразно, даже когда язык действительно лучше по многим параметрам).
я научился писать простые сниппеты без ошибок с первого раза.
Это очень ведь дорого...
Зависит от того, с чем сравнивать:
- Можно взять php, начать писать на нём прямо здесь и сейчас, очень дешево вначале, дорого, когда в файле больше 200 строк и очень дорого в поддержке
- Можно взять js, на котором тоже можно быстро начать писать, но неявные приведения типов могут склонить вас сменить профессию с программирования на проституцию
- Можно взять C++, относительно дешево выучить его, писать проектики, а потом три месяца дебажить неопределенное поведение силами двух программистов с суммарным опытом в 20 лет.
А можно взять и начать писать на Rust, в котором отсутствуют вышеперечисленные проблемы, но, блин, компилятор слишком строг. Ругается иногда. Не дает компилировать с первого раза код, который жалкий мешок мяса почему-то считает валидным.
Кому что. Я свой выбор сделал в пользу Rust. Есть моменты, о которых я жалею:
- отсутствие большого количества нормальных production-ready пакетов, на данный момент на вкус и цвет 10000+ пакетов, годных и вылизанных от и до всего чуть больше 100-200 (а у С++ и того нет). Поправьте, если ошибаюсь.
- неоправданно большой граф зависимостей для некоторых пакетов (как если бы 90% npm зависили от left-pad).
- 200 пакетов компилируются за минуту, хотелось бы быстрее
- На данном этапе не понимаю, как интегрировать Rust futures в Js Promise для асинхронного WebSocket, чтобы перенести свой проект из нативного приложения в WASM. Решения на колбеках есть, но хотелось бы чего-то готового и поддержки в tokio/romio. Программистам на Go в этом плане сильно повезло, у них таких проблем нет. Они находятся на этапе: "почему мой hello world на WASM весит несколько мегабайт. Какой рантайм, какой ГЦ? Что это такое? Почему плохой транслятор GO-WASM тащит весь язык в файлик .wasm?"
- устал от HR, приглашают на работу и собеседования каждую неделю. Приходится вслушиваться в произношение людей из Шотландии и Новой Зеландии
- приходится скрывать доходы от друзей, потому что зарплату в $$$ реально девать некуда.
Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости. Условно php7 может дать 30-40% от максимума; java — 60%; golang — 80%; и rust, c — 100%. Но в то же время чем ближе к C, тем каждую фичу ждать дольше и дольше, это с одной стороны. С другой, мы можем оценивать задачу по тому, сколько ей надо производительности и соответственно выбирать инструмент.
Про $$$ и golang вы тоже будете удивлены. Скажем так, 150-170k usd год на удаленной работе — не есть большая сложность.
Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости.
Почему вы акцентируете внимание на скорости, когда это не самое главное? Да, Rust чертовски быстр, и может быть быстрее C, но это не важно. Вы же понимаете, что люди пишут программы не для компьютера, а для других людей?
Когда мне надо наговнякать хоум страничку для друга, на которую никто не будет заходить — я выбираю PHP.
Когда мне надо в браузере работать с UI — я выбираю JS.
Когда мне надо набросать функциональщину для проверки гипотезы — я выбираю Haskell.
Когда мне нужен самый простой язык в мире, у которого 3 разных типа обработки исключений — нет, не выбираю :D
И когда мне нужен безопасный язык с предсказуемым поведением, который может поддерживать разработчик-июнь за 400USD, я выбираю Пикачу Rust, а не тот язык, поддержка которого стоит 150-170k USD в месяц (и то не предел).
Вопрос денег подняли вы.
Как план с junior должен сработать? Через какое время на rust он сможет полноценно закрывать задачи?
И если мы начинаем говорить про программы для людей, то нужен инструмент с кодом, который можно быстро читать.
Как план с junior должен сработать? Через какое время на rust он сможет полноценно закрывать задачи?
Древние свитки говорят о двух месяцах. После чего люди коммитят в сложные участки компилятора или пишут фреймворки, которые обгоняют существующие решения по производительности. Давно открывали код gcc, который поддерживают 9 анонимусов в мире? Долгой им жизни...
И если мы начинаем говорить про программы для людей, то нужен инструмент с кодом, который можно быстро читать.
Ага. Чтобы корова меньше ела и давала больше молока ее нужно чаще доить и реже кормить.
Код надо не быстро читать, а понимать правильно и корректно. И инструменты должны давать по рукам, если человек неправильно понял.
А в статье у автора ушел год и только простые сниппеты с первого раза выходят… И это опытный разработчик.
Что-то не так в свитках...
Я лишь про то, что rust прекрасный инструмент, когда мне нужен максимум скорости.
Такое ощущение, что статью вы вообще не читали.
Читал. А вы мой изначальный комментарий?
"Хорошая статья.
Правда я придерживаюсь мнения, что язык, скорее системный, и мне проще и в пяток раз быстрее делать сервисы на го, а в раст выносить криптографию, системные вещи. Иначе цена решения становится несоразмерной."
У раста нет объективных причин быть менее продуктивным, чем какой-нибудь котлин или тот же шарп. Есть сложность с пониманием борроу чекера, но на него, как выше сказали, достаточно пары месяцев. Для стартапа который на проект выделяет полгода это наверное слишком долго. Для компании с проектом на год-два уже приемлемо. Дальше стоимость внедрения только падает.
Как же все только живут с этими проблемами на рантайме… Тесты пишут, как и разработчики rust.
Если говорить не голословно, то пока я видел одну неудачную миграцию на rust, которая сильно увеличила сроки проекта и поставила его под вопрос. Сейчас очень хочу узнать не об опыте крутых, без всякого сомнения, одиночек, а о больших и долгих проектах.
FF с его 6% кода на rust не впечатлил пока, динамика там есть, но пока не видно, чтоб он занял существенную долю.
И, повторюсь, вопрос долгой поддержки. Мне лично неясна ниша языка, пока мы не увидели проекты в долгой перспективе. У нас уже есть клевые scala, haskell, но которые слишком дороги в поддержке. Мне хотелось бы, чтобы rust не повторил эту судьбу.
Как же все только живут с этими проблемами на рантайме…
Плохо живут. Я вот на шарпе когда пишу, понимаю, что в расте такой проблемы бы в принципе не возникло, а тут про нее думать надо.
Тесты пишут, как и разработчики rust.
В шарпе вы не пишете тесты на то, что вместо числа придет «ff», «qwerty» или ящереца в стакане, в JS пишете.
В расте вы не пишете тест на то, что в многопоточном окружении ваш код не сломается, в тех же шарпах — пишите.
Если говорить не голословно, то пока я видел одну неудачную миграцию на rust, которая сильно увеличила сроки проекта и поставила его под вопрос. Сейчас очень хочу узнать не об опыте крутых, без всякого сомнения, одиночек, а о больших и долгих проектах.
В статье про PVS studio было прилично ссылок. Из того, что на слуху, можно вспомнить Parity/Exonum/Redox, например.
FF с его 6% кода на rust не впечатлил пока, динамика там есть, но пока не видно, чтоб он занял существенную долю.
Когда я последний раз смотрел статистику по репозиторию, в FF было 1млн строк кода на С, 1.5 миллиона на расте, 3.5 (или 7, не помню точно) миллиона на С++, и около 5 миллионов всякой шелухи вроде html.
И, повторюсь, вопрос долгой поддержки. Мне лично неясна ниша языка, пока мы не увидели проекты в долгой перспективе. У нас уже есть клевые scala, haskell, но которые слишком дороги в поддержке. Мне хотелось бы, чтобы rust не повторил эту судьбу.
Буквально в прошлом месяце у меня знакомый сменил синиор шарпа позицию на синиор скалиста. Не так уж у неё все плохо.
А вообще, отрицать то, что на расте разработчиков меньше, чем на других языках смысла нет. Поэтому я и решил статью написать, потому что как мне кажется, от раста вся эта его история с его «железячностью» отпугивает тонну людей. А на самом деле все сильно проще, особенно если не заморачиваться в некоторых местах.
Статистику тут обычно смотрю https://4e6.github.io/firefox-lang-stats/
Судьба parity мне не понятна пока, но тут скорее вопрос не в языке, а том, что товарищи время от времени мержат в мастер по несколько сот строк кода без ревью и тестов.
Это все молодые проекты, около года. И небольшие, ну пара десятков разработчиков.
Самое интересное в долгой поддержке и/или масштабировании команд.
Да, то верная информация.
Это все молодые проекты, около года. И небольшие, ну пара десятков разработчиков.
Самое интересное в долгой поддержке и/или масштабировании команд.
Откуда взятся проектам сильно за год, если язык только 3 года назад в 1.0 вышел? Ведь там менеджеры тоже по той же логие смотрят «пока только появилось, надо обождать, присмотреться, и только потом осваивать».
Откуда сотни разработчиков на одном проекте где-то кроме мозиллы возьмутся тоже не совсем ясно.
Более крупного пока ничего нет, но опыт самого rustc показывает, что и довольно крупные проекты вполне неплохо живут.
Пожалуйста.
Поймите, мне тоже неподдельно интересен rust, но у меня просто отличается взгляд на его применение. Маленькие команды очень опытный спецов — это точно сработает и работает.
Увидим ли мы большие команды — вопрос. Увидем ли мы переход от системного языка к общему — тоже вопрос.
Мне лично пока видится, что врядли и нет. Посмотрим.
Я извиняюсь если вопрос покажется некорректным. Но мне правда интересно. Вы бы стали писать на Golang если бы за ним не стоял Гугл? И что произойдёт если гипотетически Гугл скажет "голанг неудачен, пилим всё на тайпскрипт"?
Озвученная вами проблема — извечная проблема курицы и яйца. Никто не хочет писать на новом языке т.к. на нём не пишут толстые корпорации — которые на нём не пишут т.к. пишет мало кто, goto 1. Почти гарантирую, что если бы С++ был создан сейчас в текущем виде, он бы помер не родившись — но его держат мегатонны легаси.
Да, я выбирал язык не из-за Гугла. У меня был PHP, Python, плюс всякое редкое (по месяцу пробовал Nim, Crystal), но не было чего-то достаточно быстрого, клево себя в concurrency и строго типизированного. Рассматривал варианты C#, с которым был год опыта, когда он еще был версий 1.1-1.3, Java, С++, golang.
C# отмелся поскольку совсем другой стек все же. Хотя как язык он мне очень нравился.
Java — слишком большая штука. Ее надо брать не дополнительным инструментом, а единственным и для всего. Но окончательно я ее не отметал.
С++ — я еще помню долгие споры об Oberon/modula/Pascal vs C/C++ и брать язык с всевозрастающей собственной сложностью — это точно нет.
Golang обещал полную обратную совместимость (слово сдержано и с 1.0 по 1.13 ломающих изменений было ровно 2, которые фиксились автоматически гошной же тулзой), приятное мне смещение внимания с языка на продукт (когда можно не изучать и изучать язык каждый год, а заниматься развитием продуктов), почти полная имплементация CSP (за исключением операции удаления потока), очень (очень-очень) быстрая компиляция, что делала работу в TDD удобной и комфортной. Это перевесило, я начал его учить. Скоро и вакансия нашлась.
Нет, про гугл я тогда не думал. В основном думал про те задачи, которые хотелось решать. А это был e-commerse и highload. Там golang себя хорошо нашел. Сейчас занимаюсь распределенными системами и golang себя все еще хорошо чувствует. Как бонус получил golang на мобильных устройствах и есть опыт уже разработки гошных либ для мобильных приложений.
Но не хватает иногда быстрой числодробилки. Можно пойти путем C и его биндингов в Golang. Но rust, хоть также идет по пути усложнения и увеличения объема языка, как С++, но обещает хорошую модель конкуренции и быструю числодробилку, поэтому решил его тоже взять.
Как-то так. Не думаю, что ваше дальнейшее рассуждение о курице и яйце ко мне применимо. У меня другие критерии.
Вы круты, бесспорно, но это разговор о нравится-не нравится пошел. Мне он мало интересен, в отличие от инженерных задач и подходов к ним.
мне проще и в пяток раз быстрее делать сервисы на го
Это вы начали давать субъективные оценки. Я вам лишь ответил на вашем же языке.
Отнюдь, я говорю о скорости и простоте разработки, а не о нравится-не нравится.
Простите, но мне это напоминает старую статью про го, где разработчик откпзался от него потому что "не чувствовал себя умным", когда писал гошный код.
Мы вроде как задачи решать должны в поставленные сроки и с заданными условиями, а не про вкус и цвет.
У меня есть в команде разработчик: 2 года go, затем год rust, и вот снова go.
На нем удобно сравнивать. На нем и сравниваем.
Задачи на го идут шустрее в разы.
Ну и я тоже изучаю раст сейчас, пока то, что я вижу точно говорит:
- Код ревью будут сложными и могут быть долгими
- Вход разработчика в проект тоже долгий
- Начала работы новичка в языке — несколько месяцев и точно нужно приставлять наставника.
Изучаю дальше.
Для меня вопрос открытый, хоть и есть свое мнение.
Остальное — библиотеки, стандартные паттерны и всё такое, что нужно знать в любом языке.
Это вопрос, с какой скоростью можно понимать чужой код на rust.
Раст это не «новый С++», там нет непересекающихся подмножеств языка, где каждый разработчик пишет на «своём» диалекте и не понимает соседей.
Новая версия раста выходир за в 6 недель, и как правило выходит какая-нибудь мелкая фича, как правило, снятие существовавшего раннее ограничения, стабилизация пары полезностей в стандартой библиотеке и прочая мелочь. Раз в год примерно выходят крупные фичи, вроде того же NLL, или готовящегося async/await и генераторов. Не сказал бы, что это прям очень часто, какой-нибудь C# обновляется примерно так же раз в год.
Раст это не «новый С++», там нет непересекающихся подмножеств языка, где каждый разработчик пишет на «своём» диалекте и не понимает соседей.
Мне это пока сомнительно. Пока выдится именно так. И повторюсь, в сравнении с golang, который поддерживает полную обратную совместимость и даже golang 2.0 взял на себя гарантию, что код любой версии 1.0 будет компилироваться без изменений, изменения в rust смотрятся дополнительной ценой, которую надо платить.
Возможно через полгодика изучения языка количество фич и особенностей не будет казаться таким большим, но пока оно именно такое и пока думается, что это вряд ли изменится, все же язык явно наследник именно c++.
Мне это пока сомнительно. Пока выдится именно так. И повторюсь, в сравнении с golang, который поддерживает полную обратную совместимость и даже golang 2.0 взял на себя гарантию, что код любой версии 1.0 будет компилироваться без изменений, изменения в rust смотрятся дополнительной ценой, которую надо платить.
В расте тоже полная обратная совместимость. Более того, Rust 2018 остается совместимым настолько, что вы можете иметь крейт 2018, который ссылается на 2015, который тоже ссылается на 2018, и всё это будет работать.
А вот будет ли совместимым Go 2.0 — не уверен. Судя по генерикам — вряд ли.
Возможно через полгодика изучения языка количество фич и особенностей не будет казаться таким большим, но пока оно именно такое и пока думается, что это вряд ли изменится, все же язык явно наследник именно c++.
Мое мнение, что аналогия ложная. Впрочем, вам решать.
Ну хорошо, если так.
Просто я помню .Net 2.0 с генериками, единственный абсолютно несовместимый с предыдущими версиями рантайм, ну и генерики очевидно влияют на механизмы перегрузки, который может поменять семантику уже существующего кода (в го ведь есть перегрузка?).
Если команда го всё это смогла учесть, то могу их только поздравить.
А я знаю пример, когда разработчик "въехал" и начал нормально писать на расте за пару недель. До этого он писал на го и скале.
На мой взгляд, код Раста трудночитаем -> мало кто будет на нем писать со всеми вытекающими. Так что согласен.
Тут, скорее, было интересно понять, как складывается отечественное сообщество вокруг языка. Оно определенно складывается. И, к сожалению, должен признать, что оно пока более дружелюбно, чем в свое время гошное. Хотя и тут не без криков о вкусах и понятий вроде «ненавижу язык Х»!
А вы, товарищ, не пробовали rust? Было бы интересно обсудить.
Захочется приключений — вернусь в dlang — он на 5 лет старше и хотя бы прошел детские болезни и оброс фремворками. Ну или уж посмотрю на golang — он практически стабилизировался (еще бы ввели ожидаемую обработку ошибок).
Dlang — сурово. Вы второй человек, которого я встречаю, кто на нем программирует. Удачи вам с ним, уж не знаю, что за задачи у вас.
Захочется приключений — вернусь в dlang — он на 5 лет старше и хотя бы прошел детские болезни и оброс фремворками.Где-то можно посмотреть список живых фреймворков для D? На слуху как-то кроме Vibe.d ничего и нет.
Но D я тоже хвалить не буду — у него тоже есть свои критические (на мой взгляд) недостатки.
На мой взгляд, код Раста трудночитаем -> мало кто будет на нем писать со всеми вытекающими. Так что согласен.
Посмотрим. Мне изначально код раста казался очень даже читаемым, и постепенно это чувство только крепнет. Ну вот возьмем например мою версию телеграм-клиента. Он очень простой, умеет вызывать несколько методов удаленного сервера, и держать соединения. Какие конкретно места по-вашему тут трудночитаемые?
И ведь подумал, что надо дописать: "хотя с документацией в Rust все в порядке", но не стал. А зря :)
Без документации — она потребуется позже, чтобы нормально писать.
Rust source — WTF ???
То есть вы испытали дискомфорт от того, что не смогли понять исходник на Rust, не зная его синтаксиса? Ну да, это известная проблема. Просто часто это обобщают, и говорят, что код в принципе нечитаемый и непонятный, тогда как он таковой только для тех, кто не знаком с Rust.
Можно взять C++, относительно дешево выучить его, писать проектики, а потом три месяца дебажить неопределенное поведение силами двух программистов с суммарным опытом в 20 лет.А как именно Rust защищает от ошибок с низкоуровневой работой с неправильно выровненными данными (ведь ваша ссылку ведет на исправление именно такой проблемы)?
О, старый друг по комментариям!
Это действительно меткий вопрос, я очень рад, что вы прошли по ссылкам и осознали мою боль. Я хорошо подумаю над ответом и дам его в развёрнутом виде с пруфлинками и всем таким, когда приеду домой.
Rust не защищает тебя на 100% одним своим присутствием в проекте, но позволяет вывести строгий аргумент безопасности, которые сведут количество подобных ошибок к минимуму, если не к нулю: не пиши unsafe. Если они и возникают, то ты всегда ищешь там «где светло», а светло там, где есть unsafe.
А как именно Rust защищает
У меня вчера был сложный вечер. Меня порывало сказать: "Да никак! Это настолько сложная ошибка, что даже Rust пасует перед ней!", потому что я был под влиянием неприятных воспоминаний, когда не было никакой возможности найти зацепку в коде C++, а только лишь дебажить и дебажить. Как получить зацепку для дебага кода C++, если он весь unsafe со списком в ~200 неопределенных поведений? Ну… делать вот так o_O и искать, искать, искать. Ошибки подстерегали нас на каждом углу, даже сложению знаковых чисел нельзя было доверять. Хотя казалось бы, самая примитивная операция.
На этом можно было бы и закончить комментарий, мол, Rust не защищает, но тут в дело вступает маленький нюанс: а где бы я мог выстрелить в ногу, если бы я писал виртуальную машину на Rust? Только в unsafe. И путем нехитрых изысканий приходим к тому, что unsafe мне бы нужен был только для низкоуровневой работы с памятью. Всё. Делаем o_O на 500 строчках кода, обмазываемся тестами, перепроверяем только код сборщика мусора. Rust выигрывает не в своем фанатичном "безопасно", а в предоставлении системного подхода к поиску низкоуровневых проблем. Ищи там, «где светло».
Что делать, когда этого «светло» нет? Казалось бы, есть ноды https://nodes.tox.chat/, написанные на C, которые уже 5 лет в проде, оттебажены, в которых все хорошо, которые не падают… Ведь так?
Вот ты разработчик, у тебя падает код в 60KLOC, который 5 лет "правильно работал". Что ты будешь делать? Я бы заплакал.
Ну и в противовес: tox-rs (30KLOC) без единого unsafe. И наш сервер не падает в рандомных местах. Не утекает, не ломается. И не утечет, не сломается.
Тут стоит напомнить о свежей баге, найденной в tar, которому 40 лет:
https://utcc.utoronto.ca/~cks/space/blog/sysadmin/TarFindingTruncateBug
if you run GNU Tar with --sparse and a file shrinks while tar is reading it, tar fails to properly handle the resulting earlier than expected end of file. If the file grows again, tar recovers.
Зато на C. И очень быстра.
Ибо была ссылка на конкретную ошибку в плюсовом коде с явным намеком на то, что в Rust-е вы бы от такой были бы защищены. Но проблема в том, что там была ошибка с низкоуровневым кодом, для понимания которой нужно было опускаться на уровень аппаратной архитектуры и особенностей команд конкретного процессора. Rust бы вам магическим образом ничем бы не помог.
Я бы заплакал.Тряпка! :)
И наш сервер не падает в рандомных местах. Не утекает, не ломается.Вас может удивить, но на плюсах совсем не сложно сделать сервер, который не падает, не утекает, не ломается. Мы делали это неоднократно.
Но речь про плюсы, не про С. А вы, как и многие здесь, упорно проводите между ними знак равенства.
Грубо говоря, вы слились.
Действительно грубо.
А можете привести пример проекта вот с этими свойствами (желательно со ссылками, подтверждающими эти свойства):
на плюсах совсем не сложно сделать
сервер, который не падает, не утекает, не ломается
Мы делали это
Интересно узнать о проектах какой сложности идет речь.
Действительно грубо.Как народец-то обмельчал. Слово «слились» — это уже грубо. Суппорт старого кода всего в 60KLOC — «я б заплакал».
А можете привестиИз публично доступного у нас есть вот это. Игрушка, с собственной реализацией HTTP-сервера.
ИнтересноМне вот было интересно узнать, как Rust защищает от ошибок выравнивания данных в низкоуровневом коде. Но что-то не вышло.
Может вы поддержите предыдущего оратора?
Мне вот было интересно узнать, как Rust защищает от ошибок выравнивания данных в низкоуровневом коде. Но что-то не вышло.
Если только синтаксически значительно сужает зону, где они могут появиться, чем облегчает и кодирование, и поиск ошибки. Но не радикально.
В C/C++ то же самое достигается с помощью правильной архитектуры, строгого следования code guidlines и статического анализа. Т.е. более трудоемко. Это цена большей выразительности (на низком уровне — точно) и излишней мягкости Б. Страуструпа.
Причем я бы не стал смешивать C и С++ здесь. Т.к. в С++ столкнувшись с такой ошибкой можно было бы сделать шаблонный тип вроде properly_aligned_ptr<T> и изменить API так, чтобы интерфейс оперировал такими типами, а не голыми указателями. Тогда как в C пришлось бы следовать устным договоренностям.
Тогда как в C пришлось бы следовать устным договоренностям.
То есть когда у C++ может быть защита на уровне интерфейсов — это хорошо, а когда у Rust есть защита на уровне интерфейсов по дефолту плюс усиленная защиту на уровне типов — это хипстота, смузи и закопайте?
Понятно.
когда у Rust есть защита на уровне интерфейсов по дефолту плюс усиленная защиту на уровне типов — это хипстота, смузи и закопайте?Пожалуйста, найдете в данном обсуждение хоть одно сообщение, где бы я сказал что-то плохое в адрес Rust-а. Может хоть это у вас получится.
Но т.к. вы в разговоре о достоинствах Rust-постоянно смешиваете C и C++, то я еще раз позволю себе напомнить вам, что это разные языки, с разными выразительными возможностями и разной стоимостью написания корректного и надежного кода. Вы, видимо, просто не в курсе.
Я в курсе, что это разные языки. Я так же в курсе, что на C++ можно писать как на C (за исключением некоторых очень специфичных штук типа structure initializer). А раз это можно делать, то этим люди и занимаются because they can.
И вашем мире розовых пони существует супер-классный C++17-20 (он мне тоже нравится), но на котором можно безопасно писать только с соблюдением устных договоренностей ("Срочно пишем по CppCoreGuidelines"), а в моем мире существует махровый C++, дай бог C++11, который пахнет как гавно, выглядит как говно, на вкус как говно. Его практически невозможно отличить от C, но типа в .cpp файликах, ага.
По поводу современного C++ и CppCoreGuidelines, то могу вам сказать, что примитивные шаблоны вроде not_null или bounded_value, как и смарт-поинтеры и прочие вещи, облегчающие RAII, нормальные разработчики использовать стали задолго до. Где-то даже до принятия C++98 (шаблоны, если вы не в курсе, более-менее массово доступны стали с 1994-1995-х годов). Книги вроде Modern C++ Design и C++ Coding Standards — 101 Rules Guidelines — это 2001-й и 2004-й годы. По мерками ИТ совсем давно.
Говнокод можно наплодить везде. Счастье Rust-а в том, что до него еще говнокодеры из C++, Java и JavaScript-а не добрались. Как доберутся в массовых количествах, так запаритесь unsafe расчищать.
Счастье Rust'а в том, что на нем написать нормальный код проще, нежели наговнокодить.
Но так-то да. Скоро набегут великие "оптимизаторы" из C++, и придется расчищать и опять нюхать.
на нем написать нормальный код проще, нежели наговнокодить.Обсуждение создания двусвязных списков и древовидных структур в Rust-е вот прям яркое тому подтверждение.
Но я тоже жду, пока «оптимизаторы» из C++ перебегут к вам.
Про двусвязные списки уже давно не актуально.
Про двусвязные списки уже давно не актуально.
в листинге на 1k строк 26 unsafe, в которых спрятана примерно половина кода.
Пожалуйста, найдете в данном обсуждение хоть одно сообщение, где бы я сказал что-то плохое в адрес Rust-а.
Rust бы вам магическим образом ничем бы не помог.
Я объяснил, как именно он бы мне магически помог.
Я объяснил, как именно он бы мне магически помог.Ошибку Rust бы вам предотвратил? Нет.
Все остальное — это спекуляции на тему того, как быстро конкретный человек в конкретном коде бы нашел проблему.
У меня лет 20 назад был похожий случай. В коде по десериализации двоичных данных было что-то вроде:
float parser::get_float() {
const char * ptr = m_current_ptr;
m_current_ptr += 4;
return *((const float *)ptr);
}
На x86 работало нормально. На SPARK-е сразу же SIGBUS. И нашлось без проблем.
Все остальное — это спекуляции на тему того, как быстро конкретный человек в конкретном коде бы нашел проблему.
Да, я тоже так изначально подумал про свои мысли, поэтому так долго не отвечал.
И поэтому я полез в код GC на Rust: withoutboats/shifgrethor, (начало цикла статей).
Вы знаете, код проще читать, тесты проще писать, баги проще искать. Ночью набил пару PR.
Но это уже совсем офтопик.
Вот ты разработчик, у тебя падает код в 60KLOC, который 5 лет «правильно работал». Что ты будешь делать? Я бы заплакал.
boost::stacktrace, google breakpad, в с++20 едет std::stacktrace. Ошибка локализуется еще на этапе чтения логов, до открытия IDE. Да и в целом краши — самые легко отлаживаемые баги. А если вдруг у вас сервер вернул «3» вместо «14», где «светло»?
И наш сервер не падает в рандомных местах. Не утекает, не ломается.
Во-первых, и на плюсах де-факто пишутся безопасные программы. И это не зависит ни от моего, ни от вашего, ни от чьего-бы-то-ни-было еще мнения по поводу плюсов/раста. Во-вторых, я, помнится, критиковал то, что вы сравниваете раст 2015-ого года с с++03. Сейчас вы пошли даже дальше, и сравниваете раст с вообще другим языком, си
п.с. вообще неприятно вести с вами (растовиками) дискуссии. Вы пытаетесь навязать видение мира, в котором ни одна из программ на с++ не работает, а любая попытка его опровергнуть заканчивается ничем кроме пары минусов в карму.
Во-первых, и на плюсах де-факто пишутся безопасные программы.
Согласен. Безопасные пишутся. В 0.1%. А в 99.9% на плюсах пишутся небезопасные программы. И это не зависит ни от моего, ни от вашего, ни от чьего-бы-то-ни-было еще мнения по поводу плюсов/раста.
boost::stacktrace, google breakpad, в с++20 едет std::stacktrace.
А как это бы помогло отдебажить ошибку внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native
с кучей проходов LLVM?
внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native с кучей проходов LLVM?Так что есть в Rust для предотвращения этой ошибки? Вы поместите такой же for внутрь unsafe блока и получите те же проблемы.
Безопасные пишутся. В 0.1%. А в 99.9% на плюсах пишутся небезопасные программы
статистику в студию.
А как это бы помогло отдебажить ошибку внутри C++ функции, которую вызывал JIT код, собранный самодельной VM, которая проявлялась только на g++ -O3 -march=native с кучей проходов LLVM?
Давайте маленько притормозим и отделим мух от котлет. Неправильно сгенерированный виртуальной машиной код отношения к плюсам не имеет никакого, положить код на расте может с тем же успехом, и про ваш unsafe ничего не знает. В плюсах, для диагностики плюсового кода необходимый инструментарий имеется.
Неправильно сгенерированный виртуальной машиной код отношения к плюсам не имеет никакого
Он был правильно сгенерирован. Я спрашиваю каким образом можно отследить фреймы и распечатать stacktrace, если его нет или он покорёжен неопределенным поведением?
каким образом можно отследить фреймы и распечатать stacktrace, если его нет
я лично не сталкивался с тем, чтобы стектрейса не было. Бывал ни о чем не говорящий, но это при работе с графикой, имеющей к плюсам весьма посредственное отношение
или он покорёжен неопределенным поведением
а как это вообще возможно? Память, в которую загружается исполняемый код, обычно лочится на запись.
а как это вообще возможно? Память, в которую загружается исполняемый код, обычно лочится на запись.
При чём тут она? В слове «stacktrace» «stack» присутствует не просто так, и он на запись не лочится никогда.
Когда вы вызываете функцию в стек сохраняется указатель на следующую инструкцию (адрес возврата), указатель на стек для восстановления положения в стеке «как было» при возврате из функции (указатель на начало кадра) плюс некоторые другие данные (регистры, иногда безопасный залоченный регион для предотвращения (не слишком больших) выходов за границы массивов на стеке, иногда маркер для создания спекулятивного stacktrace на случай, если стек всё же был повреждён). Когда происходит какая‐то проблема и нужен stacktrace вы обходите адреса возврата для понимания, где что было вызвано, в чём вам помогают в первую очередь адреса начала кадра — т.е. знание, какая часть стека относится к какой функции.
Теперь вопрос — что будет, если эти адреса повреждены? Даже если в стеке есть маркеры (которых обычно нет — я слышал, что они добавляются какой‐то программой, используемой для отладки, но сейчас не вспомню, какой), то это будет спекуляцией — ничто не запрещает маркеру присутствовать в переменных, размещённых на стеке. А если их там нет, то в стеке почти наверняка будут и указатели на стек, и указатели на код, не имеющие никакого отношения к адресам начала кадра и возврата.
Вот здесь есть более подробное описание: https://habr.com/company/smart_soft/blog/234239/.
upd. имеются в виду программы, написанные в соответствии с практиками современного с++, а не с++03 код с другим флагом компилятора
Плюс если вспомнить, что некоторые проекты (типа Linux) специально абузят UB через использование одного и того же компилятора, про который известно, как он этот случай обрабатывает.
Да давайте сделаем проще. Существование доказать сильно проще, чем универсальност
но вы сами сказали, что существование доказать сильно проще. 0xd34df00d утверждал что все с++ проекты, с которыми он имел дело, имели UB. Я попросил лишь назвать число таковых, на современном с++. Вы сами прекрасно понимаете, что выкладывать примеры коммерческого софта (с которым я имел дело) я не могу.
Плюс если вспомнить, что некоторые проекты (типа Linux) специально абузят UB через использование одного и того же компилятора, про который известно, как он этот случай обрабатывает.
Вы говорите о локальном использовании implementation-defined поведения, но забываете, что раст не стандартизован вообще и в нем всего один компилятор. В терминологии с++, поведение раста на 100% «implementation-defined».
Похоже на спор о том, что в офисе без перил, дверей в лифтах и электроизоляции можно работать. Можно, конечно, — смотреть куда идёшь, не трогать оголённые провода и т.д. Но позиция людей, говорящих, что им так нравится и всё в порядке, вызывает некоторое удивление.
Вы говорите о локальном использовании implementation-defined поведения
Речь идёт о нестандартных расширениях GCC -fwrapv
, -fno-strict-aliasing
и др. Согласно стандарту соответствующие конструкции вызывают undefined behavior. Так что ядро линукса — нестандартный C и до недавнего времени могло компилироваться только с помощью GCC.
В терминологии с++, поведение раста на 100% «implementation-defined».
Есть The Rust Reference и там не написано, что всё поведение — implementation defined. Отсутствие стандарта не означает, что ничего не определено. Кстати, C был стандартизирован через 17 лет после появления — 29 лет назад, а ядро линукса написано на нестандартном C. В общем, не стоит преувеличивать ценность стандартизации.
Но позиция людей, говорящих, что им так нравится и всё в порядке, вызывает некоторое удивление
никто не говорит что ему нравится. И язык развивается в сторону безопасности. Просто вы, и многие ваши единомышленники, грандиозно преувеличиваете проблему и, соответственно, ценность её решения
Согласно стандарту соответствующие конструкции вызывают undefined behavior. Так что ядро линукса — нестандартный C и до недавнего времени могло компилироваться только с помощью GCC.
«undefined behavior» значит «поведение, не определенное стандартом», а не «ошибочное поведение». Компилятор имеет право корректно обрабатывать UB не нарушая при этом стандарт. Программа при этом, разумеется, становится не стандартной, а привязанной к конкретному компилятору. Тот же статус у всех rust программ
Есть The Rust Reference и там не написано, что всё поведение — implementation defined
но rust reference не первичен, это просто описание текущего поведения компилятора. При изменении компилятора меняется rust reference, а не наоборот.
Зачем тогда эти ключи в GCC? Да и применимость определения «корректная обработка» в случае UB вызывает сомнения. Корректная согласно здравому смыслу? У комитета по стандартизации не достаёт здравого смысла? Или что-то другое?
> При изменении компилятора меняется rust reference, а не наоборот.
Почему? Может удобнее? Как с C++ делают: обкатывают экспериментальные реализации фич в компиляторах, а потом вносят в стандарт. А то иногда нехорошо получается, как с extern template. Кстати, дополнительная реализация Rust уже есть: mrustc.
Да и применимость определения «корректная обработка» в случае UB вызывает сомнения.
Корректность ПО — его способность реализовывать требуемый алгоритм в определенных условиях. Ни больше, ни меньше. Полагаться на документированное поведение компилятора — ваше право (так же, как и в расте). Стандарт лишь описывает требования к компиляторам.
Как с C++ делают: обкатывают экспериментальные реализации фич в компиляторах, а потом вносят в стандарт
при этом чаще всего в стандарт попадает не версия из компилятора, а измененная/исправленная комитетом.
Разница огромна, и она в следующем: если вдруг какой-то метод в расте будет вести себя вопреки конкретно вашим ожиданиям, нет ни одного документа, с помощью которого можно было бы ответить на вопрос «это баг или фича (или implementation defined)?».
Кстати, дополнительная реализация Rust уже есть: mrustc.
и нет ни единого расхождения в поведении этих двух компиляторов? А если они есть, то в каком из них ошибка?
Думаю обсудят и решат, где ошибка. Если, что-то описанное в reference вызывает unsoundness в системе типов, поправят reference. Если программа скомпилированная mrustc/rustc не реализует требуемый алгоритм в условиях, описанных в reference, поправят компилятор. Если расходится поведение не описанное в reference, доопределят reference и поправят соответствующий компилятор или поправят и доопределят. Если mrustc не компилирует программу из-за того, что в rustc добавили новую фичу (о чём можно узнать у разработчиков языка, или, если хочется иметь текст, попросить их внести это в reference) — очевидно, что нужно добавить новую фичу в mrustc и reference.
Я понимаю, что тяжело привыкнуть к ситуации когда неформальный комитет по стандартизации и разработчики языка — одна и та же группа, но 17 лет ещё не прошло, так что Rust потенциально может обогнать C по срокам формальной стандартизации.
Если ...
По сути «если у 2-х из 3-х одинаково, то последний неправ». А если везде (reference/rustc/mrustc) по-разному? А если всё-таки неправы двое?
так что Rust потенциально может обогнать C по срокам формальной стандартизации.
вы спорите со мной сегодня. На сегодняшний день у раста стандарта нет.
Но речь не совсем об этом. А о том, что весь rust код на данный момент implementation-defined. И в этом, на мой взгляд, нет ничего ужасного. Я лишь указываю на иронию ситуации, в которой фанаты раста критикуют c++ за использования поведения, не определенного стандартом
По сути «если у 2-х из 3-х одинаково, то последний неправ». А если везде (reference/rustc/mrustc) по-разному? А если всё-таки неправы двое?
Сделают то же самое, что делают во всех языках при обнаружении дыр в стандарте — доопределят стандарт (в данном случае неформальный). Вам обязательно печать IEEE/ISO/ANSI нужна?
Я лишь указываю на иронию ситуации, в которой фанаты раста критикуют c++ за использования поведения, не определенного стандартом
Да нет никакого тотального implementation defined в смысле C++. Есть неформальный стандарт, частью описанный в reference, частью описанный кодом rustc. В reference явно указывается, что implementation defined, а что — нет, и что — undefined behavior.
Кроме того, никто С++ за implementation defined не ругает. Ругают за кучу возможностей получить undefined behavior в любом месте программы без явного обозначения этих мест.
Кроме того, никто С++ за implementation defined не ругает.
хм
Ругают за кучу возможностей получить undefined behavior в любом месте программы без явного обозначения этих мест.
чтобы явно пометить места, где можно/нельзя получить неопределенное поведение, надо сначала определить поведение. Чуете иронию?
Документация раста генерируется
Я говорил про The Rust Reference. Она из кода не генерируется.
хм
Нестандартное расширение компилятора и implementation defined — разные вещи. Если бы в стандарте было написано, что знаковое переполнение — implementation defined, то не понадобился бы ключ -fwrapw
, который переводит компилятор в нестандартный режим, в котором знаковое переполнение определено как two's complement.
надо сначала определить поведение. Чуете иронию?
Нет, не чую. Стандарты не снисходят с небес, а проходят процесс развития. Rust сейчас в стадии, когда рано его формально стандартизировать. Но это не значит что программа на Rust'е может делать всё что угодно, так как в The Rust Reference не написано, что, скажем, глубина анализа при выведении типов — implementation defined.
Формальная стандартизация — не магический ритуал, делающий язык лучше. Это индикатор того, что язык достиг определённой степени зрелости. Поэтому я и упоминаю постоянно 17 лет нестандартного С, чтобы напомнить что ничего особенного тут нет.
Еще надо отметить, что «нестандартные расширения» — следствия стандартизации. В расте таковых не наблюдается только из-за отсутствия стандарта, а не потому что он такой классный
так как в The Rust Reference не написано, что, скажем, глубина анализа при выведении типов — implementation defined.
rust reference вообще никакое поведение не определяет, а лишь описывает. И вообще ни один документ поведение компилятора раста не определяет
Формальная стандартизация — не магический ритуал, делающий язык лучше
это «магический ритуал», который делает компиляторы языка лучше и однообразнее.
Да ладно отмазываться. Я как Rust разработчик, страдаю от отсутствия стандарта. Приходится порождать вопросы типа таких: https://github.com/rust-lang-nursery/reference/issues/485
> порождать вопросы типа таких
Хех. Я был бы очень удивлён, если бы Rust исправлял UB в C-библиотеке.
Но так исправляет же!
https://github.com/rust-lang-nursery/reference/issues/485#issuecomment-450176082
Хм, если бы Rust менял правила работы с i8
в зависимости от того из какого языка вызывается библиотека, это было бы очень странно, если возможно.
Но — да, стандарт должен описывать всё. Собственно, то, что The Rust Reference пока не описывает всё — одна из причин отсутствия формальной стандартизации.
Погодите, еще раз. Вот у нас ситуация: «программист на языке A полагается не на стандарт, а на поведение конкретного компилятора, описанное в документации к этому компилятору».
Если A = «раст», то под неё подходит абсолютно весь код, и вы считаете, что это нормально.
Но если вдруг A = «c++», то вы орете как это плохо, «UB», «это даже не с++», «плохая распространенная ментальность общества» и прочее. И это только потому, что в с++ (в отличие от раста) есть настоящий документ, по-настоящему описывающий его поведение? На мой взгляд, вы сейчас демонстрируете запредельное лицемерие. И вам за него еще лайки ставят. Я в шоке
Вы не понимаете, что есть поведение "стандартное" (не важно, чем оно определено: действительным формальным стандартом или даже просто договоренностью) и "нестандартное", то есть известное ошибочное поведение.
При этом, если вдруг UB возникает в "стандартной" части программы, то это — ошибка компилятора. Но если оно возникает в "нестандартной" — то это считается нормой. Ибо за пределами "стандарта" компилятор снимает с себя ответственность за что бы то ни было.
В Rust есть явная, четкая граница между этими двумя мирами, в C++ — такой нет.
Если, например, gcc или другой компилятор несколько уточняет стандарт C++, то есть выдает дополнительные гарантии там, где стандарт языка от них отказывается — если такие дополнительные гарании компилятор действительно дает, а не просто мы полагаемся на текущую его реализацию — то да, корректнее будет сравнивать тогда не C++ vs. Rust, а GCC C++ vs. Rust (оставляем за скобками тот момент, что человек может писать на GCC C++ при этом думая, что он пишет на C++, и связанные с этим проблемы в будущем).
Но меняет ли это ситуацию принципиально? Является ли GCC C++ — действительно безопасной версией C++, лишенной UB или явно ограничивающей его от остального кода? Только это обстоятельство принципиально может отличить безопасность GCC C++ от C++ как такового.
Вы не понимаете, что есть поведение «стандартное» и «нестандартное», то есть известное ошибочное поведение.
Вот до тех пор, пока нет бумаги, которая трактует каким должен быть компилятор языка rust, любое поведение в нем — «нестандартное». По вашей логике это равносильно «известному ошибочному».
не важно, чем оно определено: действительным формальным стандартом или даже просто договоренностью
Вот решил я, например, сделать rust компилятор. Распечатайте мне, пожалуйста, эту устную договоренность.
то да, корректнее будет сравнивать тогда не C++ vs. Rust, а GCC C++ vs. Rust
Вы хотели сказать GCC C++ vs. Mozilla Rust?
если такие дополнительные гарании компилятор действительно дает, а не просто мы полагаемся на текущую его реализацию
С точки зрения языка, «реализация» и «компилятор» — синонимы. Да, компилятор берется гарантировать некоторые инварианты при использовании этого компилятора. Но не более
Но меняет ли это ситуацию принципиально?
Да: где документ, в котором перечислены все гарантии, которые должен давать любой компилятор языка rust?
Является ли GCC C++ — действительно безопасной версией C++, лишенной UB или явно ограничивающей его от остального кода?
спор был не об этом. Вы пытаетесь доказать, что любая программа на с++, использующая UB, содержит ошибку, даже если это поведение определено компилятором. Я пытаюсь доказать, что такая точка зрения — лицемерие
Вы пытаетесь доказать, что любая программа на с++, использующая UB, содержит ошибку, даже если это поведение определено компилятором.
Где вы вычитали такое? Сами придумали, сами разоблачили :)
В Rust есть безопасное подмножество, которое практически не содержит UB. В С++ вы можете иметь такие же гарантии для произвольного участка кода?
там правда есть сноска, что
The following list is not exhaustive
Но пока никто не сталкивался с UB, которого бы не было в этом списке.
Вы исходите из того, что неопределенное поведение из стандарта языка все же определено компилятором. Но это не всегда так. Неопределенное поведение может быть различным в одной и той же программе, скомпилированной одним и тем же компилятором, запускающейся в разное время или в разном окружении. Поэтому спор не сводится к наличию/отсутствию формально описанных случаев UB в стандарте языка, а к наличию/отсутствию UB в языке по-факту.
Поэтому спор не сводится к наличию/отсутствию формально описанных случаев UB в стандарте языка, а к наличию/отсутствию UB в языке по-факту.
вы даже не поняли предмет спора. Я говорил о том, что UB в с++ вы почему-то оцениваете по букве стандарта, а в расте — по букве документации к компилятору. Соответственно многие виды поведения, определенного компилятором с++ но не определенного стандартом вы причисляете к UB.
- ...
- Вот когда раст по IEEE стандартизуют, тогда и поговорим <- вы здесь
- ...
Серьезно, вам выше дали линк на документацию, где написано, что такое УБ. От того, что там нет шильдика "IEEE" она не стала бесполезной. Вам уже подсказали, что есть другие реализации.
И да, стандарт рано или поздно появится, думаю, в ближайшие пару лет. А вот ситуация со "случайными" уб в плюсах — нет.
Как писал товарищ выше, в любой достаточно сложной программе на С++ есть возможность проексплуатировать UB в клиентском коде, и всё держится на том, что вызывающий код обещает не нарушать гарантий. Что, кстати, подводит нас к мысли, что С++ кода не существует.
Вообще, мое отношение к UB вызвано шарпами, где за такое голову за километр отстреливают. Раст и плюсы тут вообще не при чем, наличие потенциального УБ означает, что программист написал дичь, и не надо перекладывать ответственность на вызывающий код, который должен что-то гарантировать.
Поэтому любая более-менее вменяемая библиотека на шарпах обмазана тоннами проверок аргументов на null/правильную длину/правильное состояние/версию/..., и чуть что не так, бросает исключение.
Еще раз: сравнивая с++ и раст вы даете огромную фору расту потому, что с++ оцениваете с точки зрения стандарта, а раст — с точки зрения поведения компилятора. У любого конкретного компилятора с++ многие виды UB определены. Программа может их содержать но при этом быть корректной для целевого окружения. Про такой код можно сказать, что он содержит UB, но это если оценивать его по стандарту. А вот программу на расте оценить по стандарту невозможно. Я лишь указываю на то, что проводить параллели между конкретной реализацией раста и стандартным с++ нельзя.
И да, стандарт рано или поздно появится, думаю, в ближайшие пару лет. А вот ситуация со «случайными» уб в плюсах — нет.
… и вот когда он появится, вдруг окажется, что у компилятора есть и расширения, и гарантии поверх стандарта, флаги типа "-no-standard-rule" и пр. Всё то самое, за что тут критикуют плюсы.
Серьезно, вам выше дали линк на документацию, где написано, что такое УБ. От того, что там нет шильдика «IEEE» она не стала бесполезной. Вам уже подсказали, что есть другие реализации.
Эта документация даже не претендует на полноту описания даже конкретной реализации:
For now, this reference is a best-effort document. We strive for validity and completeness, but are not yet there. In the future, the docs and lang teams will work together to figure out how best to do this. Until then, this is a best-effort attempt. If you find something wrong or missing, file an issue or send in a pull request.
А вы пытаетесь меня убедить что она описывает в том числе и другие компиляторы. То, что в одном компиляторе — UB, в другом может быть определено и наоборот. Подмножество гарантированно определенного во всех компиляторах поведения нигде не описано. Всё, что вне этого подмножества, формально является «неопределенным поведением».
Еще раз: сравнивая с++ и раст вы даете огромную фору расту потому, что с++ оцениваете с точки зрения стандарта, а раст — с точки зрения поведения компилятора. У любого конкретного компилятора с++ многие виды UB определены.
Если вы под компилятором имеете ввиду семейство, например gcc, давайте сравнивать на нем.
… и вот когда он появится, вдруг окажется, что у компилятора есть и расширения, и гарантии поверх стандарта, флаги типа "-no-standard-rule" и пр. Всё то самое, за что тут критикуют плюсы.
Сильно сомневаюсь. Почему-то какой-нибудь csc умеет жить без подобных флагов, уже два десятка лет.
А вы пытаетесь меня убедить что она описывает в том числе и другие компиляторы. То, что в одном компиляторе — UB, в другом может быть определено и наоборот. Подмножество гарантированно определенного во всех компиляторах поведения нигде не описано. Всё, что вне этого подмножества, формально является «неопределенным поведением».
Верно.
Ладно, этот спор уже затянулся. Проблема в том, что где выгодно, вы в одном месте смотрите на "практичность результата", а в другом на "а где формальное определение?".
Текущее описание вполне достаточно с практической точки зрения. Я знаю, что мне нельзя делать несколько мутабельных ссылок на одну область памяти, что нельзя размыеновывать невалидные указатели и пихать мусор в str тип. Если соблюдать эти правила, то всё будет хорошо. Причем UB нет никогда при использовании safe-подмножества, которым я только и пользуюсь.
Это работает. То, что это формально не специфицировано — неприятно, конечно, но практически никаких проблем с этим не бывает. И это точно не поменяется в будущем, потому что такова позиция сообщества в целом (которое рулит языком), и таковы принципы построения в целом. Ослабить эти гарантии раст не может, а если он усилит и доопределит какие-то сценарии, хуже не будет.
Проблема в том, что где выгодно, вы в одном месте смотрите на «практичность результата», а в другом на «а где формальное определение?».
вот именно за это я вас с самого начала и критикую
То, что это формально не специфицировано — неприятно, конечно, но практически никаких проблем с этим не бывает.
вот это часто верно для с++.
Из того, что отсутствие некоторых UB С++ гарантируется тем или иным компилятором, ситуация принципиально не меняется. По-прежнему C++ допускает UB на любом копиляторе, а safe Rust — нет. Вам об этом уже не один раз сказали.
Вам уже подсказали, что есть другие реализации.«Есть» и «другие» — это несколько преувеличено. Поскольку упомянутый до сих пор mrustc, мягко говоря, сыроват: mrustc works by compiling assumed-valid rust code (i.e. without borrow checking)
Да, формирование указателя за пределы элемента-следующего-за-последним-элементом-массива — UB, даже если вы его не разыменовываете.
А вот и нет:)
First of all, it is not allowed to perform pointer arithmetic (like &x[i] does) that goes beyond either end of the array it started in. Our program violates this rule: x[i] is outside of x, so this is undefined behavior. To be clear: Just the computation of x_ptr is already UB, we don’t even get to the part where we want to use this pointer!1
But we are not done yet: This rule has a special exception that we can exploit to our advantage. If the arithmetic ends up computing a pointer just past the end of an allocation, that computation is fine. (This exception is necessary to permit computing vec.end() for the usual kind of C++98 iterator loop.)
Есть правда нюанс
This seems to break the optimization. However, the C++ standard has another trick up its sleeve to help compiler writers: It doesn’t actually allow us to use our x_ptr above. According to what the standard says about addition on pointers, x_ptr points “one past the last element of the array object”. It does not point at an actual element of another object even if they have the same address. (At least, that is the common interpretation of the standard based on which LLVM optimizes this code.)
Взято отсюда
но вы сами сказали, что существование доказать сильно проще. 0xd34df00d утверждал что все с++ проекты, с которыми он имел дело, имели UB. Я попросил лишь назвать число таковых, на современном с++. Вы сами прекрасно понимаете, что выкладывать примеры коммерческого софта (с которым я имел дело) я не могу.
Ну возьмите открытый код из гитхаба. Есть же куча крупных проектов. Неужели ни один из них не подходит под ваш критерий?
Вы говорите о локальном использовании implementation-defined поведения, но забываете, что раст не стандартизован вообще и в нем всего один компилятор. В терминологии с++, поведение раста на 100% «implementation-defined».
С одной стороны, пока у нас один компилятор (а он с нами надолго, я думаю, как и csc с сишарпом не имеет альтернатив уже два десятка лет), это не так страшно.
С другой, формализованный IEEE стандарт, конечно же, хотелось бы видеть.
Теоретическая возможность использовать устаревшие языковые конструкции не влечет повсеместное злоупотребление ими. Вам же не надо ежесекундно говорить «не суй пальцы в разетку»?
В конце концов, никто не застрахован от лэгаси, и не говорите мне что его нет в плюсах
ну там, где в плюсах легаси, у раста биндинги к легаси на других языках. Это точно лучше? Или единственное достоинство раста в том, что он слишком молод для легаси?
Разумеется, легаси есть и от него так просто не деться. Что-то закапывается поглубже и вообще не трогается, что-то переписывают кусками, полностью или с нуля. Факт в том, что новый код пишется, и новый код обычно в разы короче и многократно надежнее.
Вот например с позиции «на каком языке начинать новый проект» легаси вообще не имеет значения. Раст — хороший выбор, спору нет. Но и новые плюсы достойны. Особенно учитывая, что на них вероятность наткнуться на недостающие библиотеки намного ниже.
Для всех программ, с которыми я работал и которые были сложнее пары сот строк, компилятор рано или поздно утыкался в UB.Да ладно, дался вам этот UB. Наличие UB в коде еще не означает наличие бага. Вот, скажем, такая функция:
int avg(const vector<int> & data) {
return accumulate(begin(data), end(data), 0) / data.size();
}
Она же не только UB содержит (переполнение при знаковом целочисленном сложении), но и еще ошибку (ее нельзя использовать, если data пустой). Тем не менее, будет ли означать использование этой функции в программе обязательное наличие ошибки?Вовсе нет, т.к. программист может обеспечить валидность передаваемых в такую avg() данных другими средствами. Например, это может быть прямым следствием использованного вычислительного алгоритма.
Ну и да, в Rust-е ничего не препятствует написанию такого же avg и, если программист ошибся в своих предположениях и задействовал такую avg на неподходящих данных, он получит проблемы в run-time. Да, возможно, диагностировать причину сбоя будет проще. Только вот суть в том, что сбой не был предотвращен.
Или другой пример, приснопамятный двусвязный список:
template<typename T>
class list_item {
T value_;
list_item<T> * prev_;
list_item<T> * next_;
public:
list_item(T && v) : value_{move(v)} {} // prev_ и next_ не инициализированы!
void bind(list_item<T> * prev, list_item<T> * next) {
prev_ = prev; next_ = next;
}
list_item<T>* prev() const { return prev_; }
list_item<T>* next() const { return next_; }
};
Тут работа с методами prev() и next() прямым ходом ведет к UB, т.к. если для нового объекта list_item не был вызван bind(), то возвращаться будет мусор, так что даже такой «типа защищенный» код:
list_item<T> * prev = item.prev();
if(prev) prev->bind(...);
никакой защиты обеспечивать не будет.Но, опять таки, само использование list_item в коде может быть таким, что никаких проблем из-за этого UB не будет.
Так что, резюмируя, UB легко находится практически в любой C++ программе. Но вот к реальным проблемам это ведет далеко не всегда.
a) формально UB присутствует практически в любой C++ программе, вне зависимости от ее размера и сложности. Есть операции над целыми числами со знаком — получите UB. Есть обращения по указателям — опять UB;
b) наличие формальных UB в коде не ведет автоматически к наличию багов в коде.
Контракты — это вообще отдельная тема для разговора.
Выше приведен пример функции avg. В которой есть и UB, и ошибка. Компилятор, компилируя эту функцию вообще может не иметь представления о том, где она используется и какие данные ей подсовываются. Эта функция может быть частью отдельной, повторно используемой библиотеки, которая задействуется в совершенно разных проектах.
Формально, UB присутствует там, где невозможно доказать его отсутствие.
ну, это если совсем формально. Например, если формально, в ссылку можно передать *(T*)nullptr и получить тот самый UB. Более того, компилятор скорее всего даже выкинет проверку ссылки против nullptr, если её туда добавить. Причем так можно сделать и в расте — передать сгенерированную через unsafe ссылку на null в safe код. И там, уже в safe коде, проявится UB.
Она же не только UB содержит (переполнение при знаковом целочисленном сложении), но и еще ошибку (ее нельзя использовать, если data пустой). Тем не менее, будет ли означать использование этой функции в программе обязательное наличие ошибки?
Да, будет означать.
Да, будет означать.ППЦ какой-то. Так толсто, что даже тонко.
Да нет, я серьезноПростите, но нет, серьезно воспринимать вас и ваши доводы я уже не могу. Любой идиотизм в желании разрекламировать свою любимую игрушку должен иметь свои пределы.
Если для вас это норм, то я даже и не знаю…
Код с UB — плохой, и его быть не должно.
Имеем код:
int sum(int a, int b) { return a + b; }
Это код с UB. И вот в таком случае UB, действительно, происходит:int s = sum(numeric_limits<int>::max(), numeric_limits<int>::max());
А вот в таком, уже нет:int a = sum(some_value%100, another_value%100);
Код sum один и тот же, в нем UB присутствует. Но вот использование sum может как приводить к появлению UB, так и не приводить.
Из чего легко сделать вывод о том, что присутствие в коде UB не обязательно ведет к ошибкам в этом коде. Но ведь вы утверждаете, что обязательно приведет.
А это уже ППЦ и прямое указание, что вам следует программировать на Rust-е, т.к. в языках, которые за вами сопли не подтирают, вы отстрелите ноги не только себе. Вы это умудряетесь делать даже в C#.
Так что простите, но воспринимать вас всерьез уже невозможно.
Код sum один и тот же, в нем UB присутствует. Но вот использование sum может как приводить к появлению UB, так и не приводить.
По размышлению пришел к выводу, что ситуация та же, что в unsafe расте. Так что да, ошибкой, наверное, не будет, но средств выразить на уровне языка "чувак, тут ты отвечаешь за качество результата" нет. В итоге, я могу думаю, что функция работает с любыми двумя интами, а по сути это не так.
Из чего легко сделать вывод о том, что присутствие в коде UB не обязательно ведет к ошибкам в этом коде. Но ведь вы утверждаете, что обязательно приведет.
Наличие UB означает, что можно легко отстрелить ногу, и всё. Если джун написал int s = unknown_func(numeric_limits<int>::max(), numeric_limits<int>::max());
, виноват он, или лид, который написал такую unknown_func
, и не вставил проверки инвариантов? И вне зависимости от того, кто виноват, придется потом тратить кучу времени на поиск того, почему иногда софтина падает.
Вызов этой функции является UB, на Ваш взгляд?Пожалуйста, перечитайте внимательно, то, что было написано. В коде есть UB, в коде есть ошибка. Но использование avg не обязательно будет приводить хоть к каким-либо проблемам.
ППЦ — это утверждение, что будет приводить обязательно.
Ну и, поскольку вы можете опять уйти в левые рассуждения про оптимизаторы, UB — это свойства кода. Это когда на конкретный код стандарт языка говорит разработчику: ты не знаешь, что у тебя произойдет при определенных условиях. А не знаешь потому, что гарантирование тебе точного результата на всех платформах либо невозможно, либо слишком дорого.
Компилятор может использовать UB в своих интересах. Причем компилятор-то точно знает, что будет делать код на конкретной целевой платформе. Так что UB — это не столько про компилятор, сколько про исходный код.
Человек, который использует библиотечную функцию, должен быть уверен, что всё будет хорошо. Функция выглядела бы обычно, если бы у нее были 2 версии:
unsafe int avg(const vector<int> & data) {
return accumulate(begin(data), end(data), 0) / data.size();
}
checked_int avg(const vector<int> & data) {
if (data.empty()) {
exit_or_panic();
}
checked_int acc = 0; // checked_int panics on overflow
return accumulate(begin(data), end(data), acc) / data.size();
}
И пользователю давали выбор:
- по-умолчанию функция будет безопасна, проверит размер массива, паникует при переполнении (или бросает ошибку, или возвращает ошибку в стиле Rust/Go)
- небезопасный вариант, когда ты 100% уверен во входящих данных, но если подсовываешь во входные данные, на выходе получаешь UB
Я доступно объяснил?
if (std::distance(begin(data), end(data)) == 0)Теперь понятно, откуда берется говнокод на C++, о котором вы постоянно говорите.
:D да, тупанул. Позор мне.
Тем не менее эта помарочка все, что вы можете сказать? Никакого прозрения не вызывает? Или так же в голове сплошная безнадега: "Раст плохой, потому что я же учил C++ 10 лет, я не могу выкидывать свой устоявшийся подход к решению проблем на помойку"?
Тем не менее эта помарочка все, что вы можете сказать?Все, что я хотел сказать, я уже сказал, а именно: сторонники Rust-а автоматически приравнивают UB, существующие в C++ном коде, к обязательно и неизбежно возникающим багам. Что вовсе не так.
«Раст плохой, потому что я же учил C++ 10 лет, я не могу выкидывать свой устоявшийся подход к решению проблем на помойку»?Однажды я уже попросил привести цитату из данного обсуждения, в которой я бы называл Rust плохим. Вы не смогли этого сделать (как не смогли подтвердить и другие свои выпады). Могу попросить еще, но результат будет таким же.
И суть вовсе не в том, что я судорожно держусь за C++ из-за страха потерять накопленный опыт. А в том, чтобы разговаривая про технологии разговор шел предметный и объективный. Со стороны упоротых растоманов, так уж получается, этого никогда не происходит. Сплошные передергивания и голословные утверждения, а иногда и явные проблемы с логикой.
Когда же люди, которые выливают потоки говна на тот же C++, демонстрируют, что не могут нормально написать простой фрагмент кода на C++, но при этом ссылаются на говнокод, который их якобы окружает, это лишь добавляет красок. Особенно, когда эти же люди не могут подтвердить свои слова.
Когда же люди, которые выливают потоки говна на тот же C++, демонстрируют, что не могут нормально написать простой фрагмент кода на C++
Да, согласен. Я опозорился на полной фигне. На вашем месте я бы тоже с подозрением ко мне относился. Но это все субъективщина.
Однажды я уже попросил привести цитату из данного обсуждения, в которой я бы называл Rust плохим.
Да-да, все мы читали ваш бложик, где вы с радостью поносили Rust. Прям вершина объективной критики, ага.
где вы с радостью поносили RustДумаю, вы путаете потоки говна в адрес упоротых растоманов с критикой в адрес языка Rust.
По поводу синтаксиса Rust-а я, действительно, высказываюсь негативно. Тут за все время наблюдения за Rust-ом ничего не поменялось.
Какие-то еще примеры «поношения Rust-а» можете привести?
Все, что я хотел сказать, я уже сказал, а именно: сторонники Rust-а автоматически приравнивают UB, существующие в C++ном коде, к обязательно и неизбежно возникающим багам. Что вовсе не так.
Спасибо, Кэп. Более того я скажу, само возникновение UB — формально, не обязательно приводит к багам (ибо оно "неопределенное"). Проблема, о которой говорят вами ненавистные сторонники Rust-а — это то, что баги в таком коде могут появиться легко и незаметно. Вы с этим спорите? Если нет, то вопрос закрыт. А читать ваши сетования на сторонников Rust-а с выдуманными же вами взглядами — как-то совсем кисло.
приравнивают UB, существующие в C++ном коде, к обязательно и неизбежно возникающим багам
А если сказать так: «к багам, которые могут проявиться при любом изменении окружения»? Если программа скомпилирована один раз — конечно, в ней баг либо уже есть, либо и не будет (ну, если, конечно, не сломается что-то в системных вызовах). Но UB на то и UB, что любое изменение условий компиляции, вплоть до минорной версии любого звена, может его изменить. В том числе и превратить его в баг. Такой вариант звучит более разумно?
Похоже, проблема в том, что мои оппоненты здесь воспринимают слова "присутствие UB" как "эксплуатацию UB". Что вовсе не одно и тоже. Давайте попробую еще раз пояснить это на примерах.
Знаковое целочисленное сложение. Функция int sum(int a, int b) { return a+b; }
содержит UB, но не эксплуатирует его. А вот какой-нибудь Вася Пупкин может эксплуатировать этот UB исходя из предположения, что на его платформе переполнение int-а ведет себя так же, как и переполнение unsigned int. Что, естественно, может когда-нибудь выйти боком, если код Васи Пупкина запустят на какой-нибудь экзотической платформе, где целочисленное переполнение приводит к аппаратному прерыванию.
А вот код Пети Иванова, в котором используется тот же sum, не будет эксплуатировать UB. Например, потому, что входные данные для sum жестко проверяются вычислительным алгоритмом.
Соответственно, в sum UB присутствует, это может эксплуатировать и может приводить к проблемам. А может и не эксплуатироваться и проблем не будет вне зависимости от среды исполнения и версий компилятора.
Другой пример, знаменитое удаление файлов из-за обращения по нулевому указателю: https://gcc.godbolt.org/z/dFk_fk
Проблема здесь возникает из-за эксплуатации UB. И поведение при эксплуатации UB, как раз и будет зависеть от среды и от компилятора.
Однако, в этом случае изначальная проблема вовсе не в том, что UB приводит к удалению файлов на диске. А в том, что программа, в которой это происходит, некорректна.
Развивая этот пример давайте представим, что Вася Пупкин решил, что если у него на платформе доступ по нулевому указателю приводит к сегфолту, то он будет просто занулять указатели, но никогда не будет их проверять. Мол у меня алгоритм такой, что в живых данных нулевого указателя быть не может, а если я ошибся с алгоритмом, то обращусь по нулевому указателю и мою программу прибьют из-за сегфолта. И так оно и происходит, пока в один прекрасный момент не меняется компилятор или среда исполнения… Проблема здесь возникает из-за эксплуатации UB.
Так вот, я говорю о том, что практически в любой C++ программе может присутствовать UB. Что вовсе не означает, что в этой программе UB эксплуатируются. Поэтом и говорю о том, что наличие UB не обязательно ведет к ошибкам.
А вот эксплуатация UB — это прямой путь к проблемам.
Собственно, поэтому можно рассуждать о том, как часто в C++ коде эксплуатируются UB (сознательно или нет), насколько просто непреднамеренно задействовать UB и т.д.
Но вот говорить о том, что корректных и надежных приложений на С++ нет из-за наличия UB в коде — это неправильно. Поскольку, как уже было показано, UB в коде может быть, но он не будет эксплуатироваться. И на это не будет влиять ни смена окружения, ни смена компилятора.
Ну да, об этом и речь: в языке, где UB на каждом шагу, можно запросто перейти от его наличия к его эксплуатации, хотя бы даже случайно. И этот переход, с ростом размера кодовой базы, почти наверное происходит.
в языке, где UB на каждом шагу, можно запросто перейти от его наличия к его эксплуатацииВы путаете причину и следствие. Не наличие UB в языке провоцирует баги в коде, а баги в коде (внесенные разработчиками) приводят к проявлению UB.
Попробуйте найти примеры корректных вычислительных алгоритмов в которых бы эксплуатировались UB вообще. Вам будет трудно это сделать. В особенности таких UB, как разыменование нулевого указателя, обращение к элементу массива по неверному индексу или использование старого указателя после вызова realloc.
По сути, ваши (да и многих других) претензии к обилию UB в C++ сводятся к тому, что в случае, если вы допустили ошибку в своем коде, то вы не знаете, что именно произойдет.
Грубо говоря, вы хотите, чтобы обращение по нулевому указателю всегда было определено и всегда приводило к segfault-у.
Однако, изначальная проблема в том, что что программа, в которой есть такое обращение, некорректна. И то, что она некорректна, нельзя исправить гарантировав segfault при обращении к nullptr.
Критикуя C++ ты критикуешь инструмент. Неважно, какой язык ты используешь, важно твое умение программировать.
Очень часто люди аргументируют серьезные минусы языка, движка или фреймворка тем, что это всего лишь инструмент. Многим этот аргумент кажется правильным, но это, по большому счету, лукавство. Да, несомненно, язык — это инструмент. Но если инструмент плох или неудобен в обращении, то от мастера требуется больше знаний/сил/времени. Поэтому два одинаковых мастера будут показывать разные результаты с разными инструментами. Надеюсь, что аналогия понятна.
(с) link
Ну а то, что вы ссылаетесь на статью, где на полном серьезе говорится «Обратная совместимость с Си как маркетинговая фича» и под это еще и какая-то база подводится, больше говорит о вас и о вашем знании предмета.
И я в очередной раз обращаю внимание на то, что наличие UB в языке имеет лишь косвенное влияние на корректность кода, поскольку в корректном коде UB практически не эксплуатируется.
Ну давайте тогда придем к выводу, что в таком случае корректного кода на С++ кроме никто никогда не пишет, и закроем тему.
Ну а то, что вы ссылаетесь на статью, где на полном серьезе говорится «Обратная совместимость с Си как маркетинговая фича» и под это еще и какая-то база подводится, больше говорит о вас и о вашем знании предмета.
С++ изначально и был better C. То, что они затем разошлись, никак не повлияло на его изначальное позиционирование.
И да, это никак не отменяет цитаты выше.
P.S. до сих пор непонятно, почему мы про плюсы говорим бтв. Я же изначально говорил про высокоуровневые вещи и удобство разработки, а скатилось всё опять в «а вот в плюсах УБ». Даже если это (не) так, то разговор не об этом, и срачей про «rust vs C++» хватает. Хотел раз в жизни обсудить сравнение с котлином и шарпами, так и тут как-то разговор свернул не туда.
Ну давайте тогда придем к выводу, что в таком случае корректного кода на С++ кроме никто никогда не пишет, и закроем теЯвное противоречие с окружающей реальностью не смущает? Ну ОК.
С++ изначально и был better C.Матчасть нужно учить.
Хотел раз в жизни обсудить сравнение с котлином и шарпами, так и тут как-то разговор свернул не туда.Ну так сравнили бы в тексте статьи, тогда может и пошел бы разговор в нужное русло. А если статья просто о Rust-е, то противопоставление его с плюсами неизбежно, т.к. и тот и другой находятся в нише нативных языков без GC. И разработчикам на Kotlin или C# эта ниша мало интересна.
Явное противоречие с окружающей реальностью не смущает? Ну ОК.
Реальность про то, что есть функции, которые подразумевают UB при некотором входе, и всегда найдется код, который этот UB триггерит. Пусть даже через стектрейс в 20-30 вызовов.
Матчасть нужно учить.
Ну да, C with Classes задумывался как совершенно другой язык, а совместимость с кодом на С… ну, случайно получилось.
Ну так сравнили бы в тексте статьи, тогда может и пошел бы разговор в нужное русло. А если статья просто о Rust-е, то противопоставление его с плюсами неизбежно, т.к. и тот и другой находятся в нише нативных языков без GC. И разработчикам на Kotlin или C# эта ниша мало интересна.
То, что у него нет GC в контексте статьи нам не интересно.
Реальность про тоРеальность — она про то, что большинство комментариев в этой теме, наверняка, написана из браузеров, реализованных на C++. Но ведь корректных программ на C++ не бывает, ведь так?
Ну да, C with Classes задумывался как совершенно другой язык, а совместимость с кодом на С… ну, случайно получилось.Матчасть поучите.
То, что у него нет GC в контексте статьи нам не интересно.Если вы хотите сравнения с Kotlin/C#, то отсутствие GC — это крайне важно. И, по моему скромному мнению, ставит крест на использовании Rust-а для прикладной, не привязанной к системщине, разработке. Ибо мало что дает такой прирост продуктивности прикладного разработчика, как GC.
Ведь если в прикладном коде, который учитывает движение товаров по складам, разработчику потребуется собственный двусвязный список или граф объектов (особенно с циклами), он не будет парится с Rust-овскими лайфтайми, а воспользуется преимуществами, которые ему дает GC.
Реальность — она про то, что большинство комментариев в этой теме, наверняка, написана из браузеров, реализованных на C++. Но ведь корректных программ на C++ не бывает, ведь так?
ну дак, браузер и некорректен. Сколько багов в нем починено, и всё еще проблем хватает.
Матчасть поучите.
расскажите вашу точку зрения.
Если вы хотите сравнения с Kotlin/C#, то отсутствие GC — это крайне важно.
Я так не считаю, о чем и статья. GC дает примерно столько же преимуществ, сколько съедает. Главное — отсутствие ручного управления памятью, а не наличие гц.
Ведь если в прикладном коде, который учитывает движение товаров по складам, разработчику потребуется собственный двусвязный список или граф объектов (особенно с циклами),
В обычном прикладном коде никто не пишет свои графы и списки. В прикладном коде используются 3 колелкции: массивы, списки, хэшмапы. Всё.
он не будет парится с Rust-овскими лайфтайми, а воспользуется преимуществами, которые ему дает GC.
Всегда вместо лайтаймов можно клонировать всё подряд, и закрыть для себя этот вопрос.
ну дак, браузер и некорректен.Настолько, что не позволил вам написать очередной комментарий? O_o
А LLVM постоянно мешает вам собирать ваш Rust-овый код, ведь LLVM на C++.
расскажите вашу точку зренияПодробности хорошо изложены в «Дизайн и эволюция языка C++». В двух словах, Страуструпу нужен был язык Simula, но с производительностью языка C. И C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.
Начало 1980-х — это совсем другое время, LLVM тогда не было.
Главное — отсутствие ручного управления памятью, а не наличие гц.Так у вас выбор в принципе не большой: либо ручное управление (пусть даже с полуавтоматическими средствами, вроде подсчета ссылок), либо полностью автоматическое, т.е. GC. Вот в Rust-е GC по итогу не оказалось. Что дает ему серьезное конкурентное преимущество в одной нише. Но точно так же оказывается тяжелым гандикапом в другой нише.
В обычном прикладном коде никто не пишет свои графы и списки.В мой фразе было ключевое слово «если». Вы этого не заметили, что не удивительно.
Кроме того, «отучаемся говорить за всех» (с)
Подробности хорошо изложены в «Дизайн и эволюция языка C++». В двух словах, Страуструпу нужен был язык Simula, но с производительностью языка C. И C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.
Для чего он был выбран?
Так у вас выбор в принципе не большой: либо ручное управление (пусть даже с полуавтоматическими средствами, вроде подсчета ссылок), либо полностью автоматическое, т.е. GC
Это не правда. Ownership вполне можно рассматривать, как автоматическое управление без GC.
В мой фразе было ключевое слово «если». Вы этого не заметили, что не удивительно.
Ну *если*, нужно, то раст хуже подходит, не вопрос. С этим я нигде и не спорил.
Для чего он был выбран?Повторю специально для вас:
C был выбран для экономии сил, т.к. в случае с С не нужно было делать ни бэк-энд для компилятора, ни линкер, ни стандартную библиотеку, ни другие инструменты, которые уже существовали для C++.Но если и это вам не понятно, то тогда, специально для вас, пара-тройка аналогий из современного мира:
* LLVM был выбран разработчиками Rust-а вместо того, чтобы делать полностью свой компилятор с нуля;
* JVM был выбран для Scala, Gosu и Kotlin для того, чтобы не делать run-time языка с нуля;
* .NET для выбран для F# для того, чтобы не делать run-time языка с нуля (да и stdlib так же).
В начале 1980-х таких опций у разработчиков ЯП не было. Поэтому Страуструп вместо того, чтобы делать все с нуля, сделал лишь Cfront — транспилер из C++ в С, что позволило преиспользовать существующие компиляторы C, ликеры, профайлеры и пр. инструменты. А так же stdlib C. Ведь в изначальном C++ стандартная библиотека собственно языка C++ включала разве что iostreams. Даже STL-я тогда не было.
Это не правда. Ownership вполне можно рассматривать, как автоматическое управление без GC.Я уже понял, что всерьез вас воспринимать нельзя и вам еще (в лучшем случае) многому нужно учиться. Но все-таки читайте внимательно. У меня было ключевое слово: полностью автоматическое управление.
Лакмусовая бумажка — это способность механизма управления памятью справляться с циклическими ссылками. Если механизм не может делать это корректно без участия разработчика, то это не GC. Вот в Rust-е не GC.
А в Java, C#, Kotlin, Go, Eiffel, D и куче других языков с GC, циклические ссылки разруливаются автоматически. Разработчику нет надобности прибегать к использованию weak-references или чего-нибудь еще.
Ну *если*, нужно, то раст хуже подходит, не вопрос. С этим я нигде и не спорил.Тем не менее, вы хотите сравнивать Rust с Kotlin/C#. В этих языках полноценный GC, в Kotlin к тому же еще и null safety, насколько я помню, присутствует. Поэтому в плане прикладного программирования Rust мало кому интересен, в отличии от Kotlin-а.
Повторю специально для вас:
Я спрашиваю, не почему был выбран именно С, а для чего он был выбран? Чтоыб огурцы солить? Чтобы посмотреть на него и выкинуть? Чтобы сделать надмножество этого самого языка, переиспользовав инфраструктуру?
Я уже понял, что всерьез вас воспринимать нельзя и вам еще (в лучшем случае) многому нужно учиться. Но все-таки читайте внимательно. У меня было ключевое слово: полностью автоматическое управление.
В моем прикладном коде Rc/… не
Так ли страшен Rust, как его малюют