Комментарии 78
C++, напротив, позволяет вручную управлять памятью с помощью соответствующих ключевых слов и функций: new, delete, malloc и free.
Уже давно ручное управление памятью является bad practice. RAII-концепция захватывает все больше вещей в С++, начиная с просто умных указателей, заканчивая автоматическим управлением потоками - std::jthread. Проблем конечно хватает даже с умными указателями. Но так манипулировать тоже не стоит.
Проблем конечно хватает даже с умными указателями.
Одна из - перемещение не отслеживается. Но у Rust так же реализовано топорно - если вы переместили внутри некого блока, который даже никогда не исполнится и потом попробуете использовать возможно перемещенное значение - оно уже не даст скомпилировать такой код.
Мне кажется там вообще не про это сказано. Под "вручную упаавлять" имеется в виду скорее сама возможность вручную выделить память, не важно в конструкторе это сделано или нет.
Привет,
только что разместил здесь этот скриншот.
Давайте обсудим, как это может быть.
BTW эта зависимость существует для всех проблем, которые я решил там.
Я имею в виду время и память.

Может быть, все VM для Python и C/C++ перегружены, а для Rust - нет?
Насколько я понимаю память считается вместе с виртуалкой/докером, на которой запускается С/С++. Она же даёт какой-то оверхед на рантайм. Минимальный размер по памяти у С++ в районе 10 мб. На некоторых задачах видел ~8, но никогда меньше. У раста минимальный рантайм отъедает примерно 2 мб.
А так - если алгоритмы написаны одинаково на Rust и на C/C++, то рантайм на литкоде будет различаться на пару миллисекунд. Судя по отъедаемой памяти - код не был эквивалентен.
Ещё один нюанс - автовекторизация. С/С++ используют два компилятора - gcc и clang. rust только clang. Поэтому велик шанс, что у раста сгенерировались векторные вставки, а у плюсов - нет.
Третий вариант - то как читаются данные. Раньше для ускорения можно было использовать олимпиадный трюк
auto gucciGang = []() {
std::ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}();
Но похоже в какой-то момент они это прописали где-то в своём бойлерплейте и трюк больше не добавляет ускорение. Повторные ресабмишны С++, кстати, примерно по той же причине могут отдать сильно иные цифры.
алгоритм был тот же
возможно, между 1-м и 2-м решениями Python были небольшие различия
Я не знаю какие у питона базовые значения. 80мс вполне может быть статистичским мусором у питона. Речь за сравнение С, С++ и Rust. При прочих равных их память +- должна быть одинаковая + упомянутый околоконстантный оверхэд. Определённо не ситуация на скриншоте, где значения и по скорости и по памяти отличаются на порядок, что говорит, что алгоритмы не были перенесены корректно между языками.
А что скажете по поводу Zig? Вроде как тоже претендент на замену Плюсов.
После коронавируса (в рамках реструктуризации) Mozilla стала увольнять сотрудников,
насколько мне известно сокращения начались раньше из-за сокращения финансирования из-за того что гугл вляпался в антимонопольную историю и сократил плату за дефолтный поисковик в браузере.
Более того, с 2021 года Google теперь поддерживает Rust в разработке приложений для Android наравне с C/C++.
А есть ссылка? Насколько мне изестно официальная поддержка раст только в проекте Fuchsia - операционная система для IoT-like устройств. Ну и в кодовой базе появилось несколько переписанных модулей, имевших застарелые проблемы безопасности, связанные с памятью, в частности bluetooth модуль.
Из-за закрытости платформ Apple язык Swift подойдет только для:
Там недавно вроде кроссплатформенность какую-то базовую завезли.
Кстати синтаксис Carbon очень похож на синтаксис Rust,
Он похож скорее на D, чем на Rust. И вероятно имеет такие же успехи стать заменой С++ как и D.
Более вероятными "убийцами" С++ могут стать Zig, CppFront и Odin.
Zig во многом похож на Go, однако не имеет сборки мусора и в среднем находится где-то между Си и Go.
CppFront - фактически C++ без легаси - то как язык должен был бы выглядеть, если бы не бремя легаси - консистентный синтаксис вместо спирали смерти C/C++, нормальные дефолты, сокращение бойлерплейта, адекватные ошибки, современные фишки, полная обратная совместимость с С++. Фактически оно всё это конвертит в тот же С++ активно обмазывая это кусками GSL, как в своё время он это делал с С++, конвертируя его в С.
Odin - ещё один С-подобный язык, во многом синтактически похожий на Go и Zig. Целью языка был лаконичный и быстрый компилятор - по заветам Джона Блоу - который можно было бы также легко написать как и компилятор Си. Синтаксис языка устроен таким образом, чтобы его рефакторинг происходил как можно проще и эволюция программы от этого страдала меньше. При этом сборочная система языка требует только компилятор этого самого языка, что опять же отлично сказывается на экосистеме.
Zig и Odin топят за ручное управление памятью, но с синтаксическим сахаром, что делает их на порядок безопаснее того же Си и C++, при том что по производительности они не уступают. У обоих имеется свой вариант Tagged Union (в Rust они представлены как enum), что также облегчает работу со многим вещами, такими как машины состояний. Ну и кажется у обоих имеется рефлексия на уровне языка.
Можно писать сколько угодно статей "Язык Х - убийца C++", но уже написанный C++ код никто полностью переписывать не будет, это потребует много времени и денег. Да и после реанимации языка, начиная со стандарта C++09, все не так плохо.
Я например просто устал от этих растаманов и прочих, которые не осилили плюсы.
Они все еще живут в страхе от new/delete, которые, как сказали выше, уже давно практически моветон.
Зато жутко уродский синтаксис ржавого никого не смущает.
нету такого стандарта
А вставки на Ассемблере или целые функции какая-нибудь из предлагаемых замен позволяет писать?
Тема осталась не раскрытой.
https://doc.rust-lang.org/reference/inline-assembly.html
А вставки на Ассемблере или целые функции какая-нибудь из предлагаемых замен позволяет писать?
А вот с C++ это работает не везде. Так, начиная с Visual Studio 2022, Microsoft больше не поддерживает ассемблерные вставки (inline assembly) в компиляторе Visual C++ при компиляции с использованием режима x64 (64-битного кода).
C++, выглядит громоздким и архаичным
Что за субстанция в голове у людей, которые вот так искренне считают...
fn funcname() -> rettype
Вместо
rettype funcname()
И эти люди смеют заикаться о громоздкости... Что касается архаичного, якобы, внешнего вида, то это чистой воды NIH-синдром и юношеское революционерство, желание стать Страуструпом 21-го века и дистанцироваться от предыдущих поколений.
На плюсах тоже можно так писать нынче. Просто бойзы не в курсе что так можно :)
Передача аргументом указателя на функцию в С и С++ - громоздкая и архаичная. Например это очень сложно читать:int operation(int(*op)(int,int), int a, int b) { return op(a, b); }
А вот это сразу очевидно:fn operation(op: fn(int, int), a: int, b: int): int { return op(a, b); }
Да, этот C синтаксис указателей не очень. Зато C++11: std::function<int(int, int)>
- разве не очевидно? Заодно можно и лямбду передать.
А в вашем растовом примере мне наоборот совершенно неочевидно, а где возвращаемый тип аргумента op
? Или можно передать с любым, главное чтобы аргументы совпадали?
А что делать, если мне не сложно читать первую запись? Куда обратиться? А вот вторая запись, напротив, вызывает вопросы.
Во-втором примере я не вижу, куда спряталась информация о типе возврата функции, указатель на которую передаётся. Или функции operation пофигу: она ждёт указатель на функцию, принимающую два инта, а что она там возвращает ей глубоко наплевать? И как в этой нотации записать не указатель на функцию, а указатель на указатель на функцию, или указатель на указатель на указатель на функцию?
Во-втором примере я не вижу, куда спряталась информация о типе возврата функции, указатель на которую передаётся.
Я чуть выше уже про это ответил.
И как в этой нотации записать не указатель на функцию, а указатель на указатель на функцию, или указатель на указатель на указатель на функцию?
Точно также, как в С, но читается лучше:
op_ref_ref_ref: &mut &mut &mut fn(int, int): int
Разумеется могут возникнуть проблемы с Borrow Checker в такой схеме, так что лучше
op_ref_ref_ref: &mut Box<Box<Box<fn(int, int): int>>>
Хотя я не могу даже представить, зачем нужно передавать "указатель на указатель на указатель на функцию".
Допустим мне нужна функция, принимающая 7 параметров типа const unsigned long int, как это будет выглядеть в С++? В Паскале к примеру F(const a,b,c,d,e,f,g:Dword)
Все относительно:
template<typename T>
void func(const T &value);
vs
fn func<T>(value: &T);
На самом деле, все разговоры про архаичность синтаксиса си/си++, и восхваление синтаксиса языка X (очевидно убийцы С/С++, по мнению автора статьи) – это что-то на уровне сравнения английского, русского, французского и китайского.
Каждый язык имеет свою структуру, и не стоит ругать или восхвалять один язык над другим (когда вопрос касается вкусовщины).
Мне и весь этот хайп с небезопасностью плюсов непонятен: если "программист" не умеет писать безопасный C++ - это в общем-то означает, что он просто не умеет писать на C++
С++ в совершенно обычном коде вызывает UB, чего не происходит в других языках. Посмотрите например последний пример по ссылке, связанный с Integer Promotion:
https://github.com/Nekrolm/ubbook/blob/master/numeric/overflow.md
constexpr std::uint16_t IntegerPromotionUB(std::uint16_t x) {
x *= x;
return x;
}
// 65535 * 65535 mod 1<<16 = 1
static_assert(IntegerPromotionUB(65535) == 1); // won't compile
Хотя казалось бы в unsigned типах всё надежно..
Обратная совместимость. Код C++ можно включать в файлы Carbon.
Вот это очень важно. Чтобы прямо в рамках одного проекта работало - старое можно до поры до времени (или навсегда) оставить на С++, новое писать на новом языке. И чтобы все среды разработки из коробки поддерживали такие гибридные проекты.
Собственно, и в рамках самого С++ можно было бы так сделать - развитие текущего С++ заморозить навсегда, выпустить некую очередную версию языка без всех костылей и легаси, несовместимую со старым кодом, назвать это С++2, старый код от нового отличать за счет другого расширения файла или #pragma version 2
в начале файла. Но чтобы можно было легко собирать гибридные проекты. Тогда у разработчиков будет стимул постепенно переходить на новый язык, новые проекты сразу на новом, в старых - добавлять новые файлы на новом языке и переписывать старый код лишь по мере необходимости (или вообще не переписывать, если таковой необходимости нет).
Не нравится старый стиль - не используй! У тебя есть древний код из эпохи древних стандартов или вообще некросишный код? Напиши враперы и юзай современные плюсы и даже новый язык придумывать не надо. Считаю, что у плюсов три серьезный минуса - это медленная сборка, распространение кода(надо запарится, что бы какую нибудь либу слинковать с проектом) и системы сборки которое шо то шо это. Одинаковое г-но.
И чтобы все среды разработки из коробки поддерживали такие гибридные проекты.
В смысле, среды? Какие вам среды нужны?
Писать проект на двух или более языках всегда было возможно, и эта возможность всегда упиралась в линкер, а не IDE или один из ЯП. Если линкер способен слинковать объектные файлы, порождённые разными компиляторов разных ЯП — милости просим.
И чтобы это происходило легко и безболезненно, мировому сообществу нужно было тратить больше усилий на стандартизацию ABI, на стандартизацию способа декорирования/замангливания имён, а не на привнесение в язык тупейшего синтаксического сахара, чем они заняты в последнее время.
По итогу - надежды на Carbon. А так пока ничего толкового нет.
Теперича C++ уже не тот, что давеча. Неудобства конечно есть, но не так чтобы сильно критичные.
У каждого языка своя ниша. Rust и Golang сравнивать с C++ некорректно, они не то что не мультипарадигменные, они даже не ООП. Rust - больше альтернатива C.
Реальной альтернативой C++ мог бы стать Dlang, но к сожалению за ним не стоят корпорации и о нём мало кто знает. На мой взгляд гораздо стройнее Carbon, Swift.
https://dlang.org/
Rust очень даже ООП. Чего не хватает в нем, чтобы быть ООП?
То что в нём есть методы и трейты не делает его в привычном понимании ООП языком.
Классический код на Rust не "ориентирован" на объекты и их взаимодействие, это скорее смесь процедурного и функционального программирования.
Извернутся конечно можно, но извернутся можно на любом языке, а в Rust придётся ещё бороться с компилятором.
Carbon — это абсолютно новый (он появился в 2022 году) компилируемый язык общего назначения от компании Google, который позиционируется как более синтаксически элегантный преемник C++.
Любой язык, который позиционируется как "убийца с++" обречён на провал. Потому что народу не нужен убийца чего-либо. Народу нужен удобный инструмент для работы. Это когда например увидел первый раз C# - и дельфи с джавой тут же стали пережитком прошлом.
А на Carbon я посмотрел ещё 2 года назад - ну не увидел никаких инноваций, чтобы с++ тут же стал пережитком прошлого (как случилось в своё время с дельфями). Увидел солянку из кучи всего от других языков. Более того, авторы не стеснялись говорить «присылайте нам свои идеи» - ну то есть взялись за разработку нового революционного языка не имея достаточного количества идей о том, а чем же он будем качественно превосходить все прочие уже существующие языки.
Централизованная экосистема - это сразу до свиданья. Не хватало ещё на уровне ЯП столкнуться с тем, что очередные чепушилы посчитали тебя человеком второго сорта и перекрыли доступ.
Ключевое слово fn это рак мозга и паскальная живопись. Компилятору оно нафиг не сдалось, а человеком двухбуквенные ключевые слова очень плохо считываются, от обилия одно- и двухбуквенных слов мельтешит в глазах, текст программы превращается в месиво. По fn можно сразу понять, что язык проектировали кретины.
У плюсового rettype funcname() тоже есть недостаток: если возвращаемый тип довольно длинный, то код начинает плохо считываться, но это решается использованием для таких случаев схемы auto funcname() -> rettype.
Ну и в целом синтаксис того же Rust это что-то с чем-то. Создатели явно хотели сделать ещё хуже, чем у С++. Неприятно даже смотреть на исходники, какая-то каша.
В целом, я не понимаю навязчивой мании убить С++. Последние стандарты сделали плюсы весьма приятным языком, на котором можно кодить, не сталкиваясь с сишными проблемами. Большинство (если не все) этих проблем из-за того, что у человека мозг уже попорчен сишкой и он пишет на плюсах, как на си с плюсами. А это совсем не обязательно.
Да, у С++ есть весьма серьёзный минус: системы сборки. Но вот эти вот мягкие кресла, предлагаемые новомодными язычками, могут в один прекрасный момент превратиться в стул ведьмы. Спасибо, уж лучше я как-нибудь поживу с табуреткой cmake.
То есть auto funcname() -> rettype лучше чем fn funcname() -> rettype?)
Ключевое слово fn это рак мозга и паскальная живопись.
Нет, ключевое слово fn (а также fun, func, function) это необходимость для упрощения компилятора, чтобы не было всяких Most Vexing Parse (а короткие ключевые слова действиительно удобнее для написания). А вот что действительно рак мозга и паскальная живопись - так это мусорные стрелочки и двоеточия при объявлении функций и переменных, которые выполняют чисто декоративную роль и визуально засоряют код.
function Add(x, y: Integer): Integer; // Pascal
fn add(x: i32, y: i32) -> i32 // Rust
fn Add(a: i64, b: i64) -> i64 // Carbon
func greet(name: String) -> String // Swift
def greet(name: String, age: Int): String = // Scala
fun greet(name: String, age: Int): String // Kotlin
fn add(x: i32, y: i32) i32 // Zig
В языке Go нет никаких стрелочек и двоеточий, и никаких проблем их отсутствие не создает.
func sum(x, y int) int // Go
Нет, ключевое слово fn (а также fun, func, function) это необходимость для упрощения компилятора, чтобы не было всяких Most Vexing Parse
Ок, принимается.
а короткие ключевые слова действиительно удобнее для написания
Для написания, да. Но не для чтения. Слово func намного легче считывается.
Насчёт мусорных двоеточий и стрелочек тоже согласен.
Отсутствие двоеточия не создаёт проблем, если пробел не перегружен. Лично я бы предпочёл избавиться от обязательных скобочек для передачи параметров в функцию, а также от fn/def/var/auto в принципе. Хочу например вместо print(sin(x))
писать просто print sin x
. А для объявления функции использовать какой-нибудь символ, а не ключевое слово, например
#AddSub (a,b):int -> (c,d):int
c=a+b
d=a-b
Почему стрелочка вам код засоряет, а ключевое слово func - нет?
Потому что ключевое слово func стоит в начале всей конструкции. Значит, все что дальше - функция. Также как struct - структура, enum - перечисление, и т.д. Первое слово всегда определяет языковую конструкцию.
И кстати, еще одно преимущество синтаксиса Go - в том, что в нем лямбда-функции объявляются в точности также как и "обычные". Т.е. вообще никакой разницы, только имя не указывается. В остальных же языках - зоопарк кто во что горазд. Какие-то вертикальные палки и прочий ужас.
[](int x, int y) { return x+y; }; // C++
^int (int x, int y) { return a + b; }; // ObjC
(x, y) => x + y // C#
(int x, int y) -> x + y // Java
delegate int(int x, int y) {return x+y;} // D
let add = |a, b| a + b; // Rust
{ (a: Int, b: Int) -> Int in return a + b } // Swift
(a: Int, b: Int) => a + b // Scala
_ + _ // и это тоже Scala
{ x: Int, y: Int -> x + y } // Kotlin
lambda x, y: x + y // Python
lambda { |x, y| x + y } // Ruby
(x, y: int) => x + y // Nim
|x: i32, y: i32| i32 { return x + y; }; // Zig
И вообще в ASCII операторных символов мало (а в языках программирования используется именно ASCII потому что эти символы гарантированно есть на всех клавиатурах мира). Т.е. операторы лучше по возможности оставить для инфиксных выражений, унаследованных от математики.
Потому что ключевое слово func стоит в начале всей конструкции.
Почему бы и определение типа не делать тоже слева, как в с++? Я не вижу удобство в том, когда какие-то определения/типы/ключевые слева, а какие-то справа. Лично мне нравится когда всё справа, но до логического конца этого ещё никто не довёл. В том же карбоне пока ещё не догадались, что вариант x=5:int
более читабелен, чем x:int=5
.
В остальных же языках - зоопарк кто во что горазд. Какие-то вертикальные палки и прочий ужас.
Ну так если всё будет по-одинаковому - то и языки друг от друга не будут отличаться.
И вообще в ASCII операторных символов мало (а в языках программирования используется именно ASCII потому что эти символы гарантированно есть на всех клавиатурах мира)
Так вроде давно уже все на юникод перешли. Повесить дополнительные символы на клавиатуру не проблема как глобально, так и локально (через автозамену в IDE, типа "?*" на "×", "?/" на "÷" и тд).
Почему только у rust лямбда сохраняется в переменную? У вас какое-то особое отношение к нему?
преимущество синтаксиса Go - в том, что в нем лямбда-функции объявляются в точности также как и "обычные". ... В остальных же языках - зоопарк кто во что горазд.
Но ведь лямбда это не обычная функция. Она может захватывать значения из зоны видимости и даже менять их. Мне, кстати, наоборот лямбда через func
не нравится в Go, но это дело вкуса.
И кстати, еще одно преимущество синтаксиса Go - в том, что в нем лямбда-функции объявляются в точности также как и "обычные.
Что делает из го просто месиво при использовании лямбд, тк куча визуального шума который тебе не нужен. В случае C# имеешь короткий понятный синтаксис, который мало с чем спутаешь.
Зачем мне в го лямбде писать типы, когда функция в которую я передаю эту самую лямбду уже объявила типы? Это просто двойная работа, которая создает визуальный шум.
А вот что действительно рак мозга и паскальная живопись - так это мусорные стрелочки и двоеточия при объявлении функций и переменных, которые выполняют чисто декоративную роль и визуально засоряют код.
В некоторых языках, це частично способ отделить принимаемые значения от возвращаемых, тк в таких языках запись по типу (value1: i32, value2: i32), может являться вполне себе валидный кодом.
В скала это нужно, тк там с помощью этого записывают каррирование функции
Хотя в целом вкусовщина, и вопросов бы не возникало, если бы вы до этого не приводили в пример го как образец, где буквально такая же проблема, когда нужно делать полную декларацию всех типов принимаемых и возвращаемых значений в любом случае.
Не факт, что множество вариантов вызова функций (со скобками и без) это хорошо.
Как, например, без скобок выразить print(sin(x) + cos(x)) ?
Если прям совсем без скобок - то используя постфиксную запись, типа как в Вольфраме: sin x + cos x >> print
(а конкретно в Вольфраме это будет как Sin[x]+Cos[x] // Print
). В порядке эксперимента и проверки концепции бесскобочный парсер (а заодно и неявное умножение) я уже делал, поэтому это точно возможно. Насколько такой подход интересен кому-то другому - вопрос уже другой, но математики в своей массе скобочки после функций без необходимости не ставят.
Обратная польская нотация полностью искореняет скобки, но вы б попробовали читать код в ней - там с ума сойти можно. LaTeXовский библиографический генератор BibTEX именно этот синтаксис использует, и для диссертации я ему ГОСТ-совместимый стиль писал, чуть с ума не сошёл.
В Вольфраме я часто использую постфиксную запись. Это удобно на самом деле, когда можно сочетать с обычной. Особенно в случае с вложенными функциями более чем из одного оператора.
Вот Хаскель, к примеру:
Prelude> let x = 1
Prelude> print $ sin x + cos x
1.3817732906760363
Prelude> print (sin x + cos x)
1.3817732906760363
Вот из F# пример, как там работает:
printfn "%f" <| sin x + cos x
Но там как бы удобно, что есть оператор <|, который работает навроде обычной передачи в функцию, но с меньшим приоритетом самого оператора.
$x = 10;
print sin $x;
# -0.54402111088937
Такое есть в Перле ))
C++ и PHP — два языка которые «вот-вот» уже должны умеритесь и надо их заменять. Но только они никак не умирают, а наоборот, только развиваются. Так может пора перестать искать «новых убийц», а применять различные технологии там, где это необходимо, без попыток сделать «X, только лудше!»?
Я не понимаю одного. Зачем менять синтатксис языка? Чтоб отличаться?
А уж невозможность в Котлине использовать в индексах массивов числа с плавающей точкой меня убило (при пяти новых вариантах написания цикла for).
Плюсы хоть морально и устарели и пока их доведут на современный уровень пройдут ещё годы... Ничего не заменит ни плюсы ни си. Просто потому что это огромное наследие от которого нельзя уйти просто так.
А что случилось с Go, что Гугл, его авторы решили ещё один язык придумать на замену?
Опять заменять C++ и убивать С? Я думал уже успокоились и больше не надо точить ножи :|
Я вообще сам на Go программирую, уже > 10 лет как, и мне очевидно, что никакого C++ он заменять не собирается и не собирался. Когда-то на заре создания языка авторы хайпа ради высказались, что это может стать заменой C++, но быстро стало понятно, что в этом нет резона. У Go своя ниша написания многопоточных сервисов, где он отлично смотрится на фоне других языков, также в принципе он неплохо подходит для написания GUI (хотя в этой области малопопулярен). Если он кого и вытеснял, то уменьшил количество вебсервисов на PHP, Ruby и Python, и то вывести их полностью вероятно невозможно.
Тем временем, один из мантейнеров Rust в ядре Linux порвался и хлопнул дверью.
I am retiring from the project. After almost 4 years, I find myself lacking the energy and enthusiasm I once had to respond to some of the nontechnical nonsense, so it's best to leave it up to those who still have it in them.
Интересно, под "нетехнической ерундой" случайно не имеется в виду недоумение, как поддерживать вот такую вот write-only дичь?
Что конкретно там write-only? Типы? Просто они описывают и гарантируют то, что возвращает функция и их не поддерживать надо, а соблюдать. Разница с Си тут в том, что разработчики свойства типов которые тут прописаны явно, держат в голове и компилятор их не проверяет.
Если уж приводить примеры ужаса, то можно ссылку на макрос скинуть.
Интересно, под "нетехнической ерундой" случайно не имеется в виду недоумение, как поддерживать вот такую вот write-only дичь?
Там же суть в том, что это просто типизированная версия того ужаса, который оставили Си-шники, ещё и без документации
Языки программирования, которые могут заменить C++ — Rust, Go, Swift, Carbon