В предыдущих сериях:



Медленно, но верно Раст проникает не только в умы сотрудников больших корпораций, но и в умы школьников и студентов. В этот раз мы поговорим о статье от студента МГУ: https://rustmustdie.com/.


Её репостнул Андрей Викторович Столяров, доцент кафедры алгоритмических языков факультета ВМК МГУ им. М. В. Ломоносова и по совместительству научрук студента-автора статьи.


Я бы сказал, что тут дело даже не в том, что он "неинтуитивный". Дело скорее в том, что компилятор раста сам решает, когда владение "должно" (с его, компилятора, точки зрения) перейти от одного игрока к другому. А решать это вообще-то должен программист, а не компилятор. Ну и начинается пляска вида "как заставить тупой компайлер сделать то, чего я хочу".
Бред это всё.

— А. В. Столяров

Сама статья короткая, но постулирует довольно большой список спорных утверждений, а именно:


  • Стандартная библиотека неотделима от языка
  • У него отсутствует нулевой рантайм
  • В Rust встроен сборщик мусора
  • Компилятор генерирует медленный машинный код

На самом деле набросов еще больше, но достаточно и этого списка.


К сожалению, для опровержения этих пунктов мне придется писать максимально уродские хэлло ворлды, которые только можно представить.


Содержание





Опускаемся на самый низ



Нулевой рантайм в Си


Честно говоря, до прочтения статьи я ни разу не встречал такого определения как zero runtime. Немного погуглив, я наткнулся на книгу А. В. Столярова ISBN 978-5-317-06575-7 Программирование: введение в профессию. II: Системы и сети, изданной в 2021 году. В главе "§4.12: (*) Программа на Си без стандартной библиотеки" приводится определение нулевого рантайма и пример программы.


Реализация подпрограммы _start (под Linux i386):


start.asm
global _start       ; no_libc/start.asm
extern main
section     .text
_start:
    mov ecx, [esp]  ; argc in ecx
    mov eax, esp
    add eax, 4      ; argv in eax
    push eax
    push ecx
    call main
    add esp, 8      ; clean the stack
    mov ebx, eax    ; now call _exit
    mov eax, 1
    int 80h

Модуль с "обертками" для системных вызовов:


calls.asm
global sys_read     ; no_libc/calls.asm
global sys_write
global sys_errno

section .text

generic_syscall_3:
    push ebp
    mov ebp, esp
    push ebx
    mov ebx, [ebp+8]
    mov ecx, [ebp+12]
    mov edx, [ebp+16]
    int 80h
    mov edx, eax
    and edx, 0fffff000h
    cmp edx, 0fffff000h
    jnz .okay
    mov [sys_errno], eax
    mov eax, -1
.okay:
    pop ebx
    mov esp, ebp
    pop ebp
    ret

sys_read:
    mov eax, 3
    jmp generic_syscall_3

sys_write:
    mov eax, 4
    jmp generic_syscall_3

section .bss
sys_errno resd 1

Простенькая программа, которая принимает ровно один параметр командной строки, рассматривает его как имя и здоровается с человеком, чьё имя указано, фразой Hello, dear NNN (имя подставляется вместо NNN):


greet3.c
/* no_libc/greet3.c */
int sys_write(int fd, const void *buf, int size);

static const char dunno[] = "I don't know how to greet you\n";
static const char hello[] = "Hello, dear ";

static int string_length(const char *s)
{
  int i = 0;
  while(s[i])
    i++;
  return i;
}

int main(int argc, char **argv)
{
  if(argc < 2) {
    sys_write(1, dunno, sizeof(dunno)-1);
    return 1;
  }
  sys_write(1, hello, sizeof(hello)-1);
  sys_write(1, argv[1], string_length(argv[1]));
  sys_write(1, "\n", 1);
  return 0;
}

И сама сборка:


nasm -f elf start.asm
nasm -f elf calls.asm
gcc -m32 -Wall -c greet3.c
ld -melf_i386 start.o calls.o greet3.o -o greet3

На машине автора этих строк (Столярова) размер файла составил 816 байт. На моей машине 13472 байта.


Что ж, применим clang-14, ld.lld-14, -Os и strip; и на моей машине получилось 1132 байта:


nasm -f elf start.asm
nasm -f elf calls.asm
clang-14 -m32 -Os -Wall -c greet3.c
ld.lld-14 -melf_i386 start.o calls.o greet3.o -o greet3
strip ./greet3

В своей книге Столяров делает очень сильное утверждение, а именно:


Но дело даже не в этой экономии (размера исполняемого файла — Прим. авт.)…
Намного важнее сам принцип: язык Си позволяет полностью отказаться от возможностей стандартной библиотеки. Кроме Си, таким свойством — абсолютной независимостью от библиотечного кода, также иногда называемым zero runtime — обладают на сегодняшний день только языки ассемблеров; ни один язык высокого уровня не предоставляет такой возможности.

Что ж, давайте разберемся, обладает ли Раст таким свойством.



Из чего состоит хэлло ворлд


Рассмотрим базовый пример, приведённый на официальном сайте языка Раст:


fn main() {
  println!("Hello, world!");
}

Так как println! — это макрос, а не функция, у нас есть возможность посмотреть на код после раскрытия макроса. Для этого воспользуемся утилитой cargo-expand:


#![feature(prelude_import)]
#[prelude_import]
use std::prelude::rust_2021::*;
#[macro_use]
extern crate std;
fn main() {
  {
    ::std::io::_print(::core::fmt::Arguments::new_v1(
      &["Hello, world!\n"],
      &match () {
        _args => [],
      },
    ));
  };
}

Компилятор вставил импорт стандартной библиотеки extern crate std; и прелюдию use std::prelude::rust_2021::*;. Именно эти неявные вставки я и хотел показать.


Стандартная библиотека — это удобный набор функций, коллекций, структур и типажей в окружении, когда у тебя есть ос, фс, куча, сокеты и прочая хипстота. Считается, что 93.9% программистам именно такое поведение (автоматическое включение std и прелюдии) и требуется.


Весь API стандартной библиотеки подробно описан в официальной документации. Есть удобный быстропоиск: https://std.rs/QUERY, где QUERY — ваш запрос, например https://std.rs/mutex.



Отключаем std


Тем не менее, для остальных 19% программистов предусмотрен режим отключения стандартной библиотеки с помощью атрибута #![no_std].


#![no_std]
#![feature(start, lang_items)]

// Говорим компилятору влинковать libc
#[cfg(target_os = "linux")]
#[link(name = "c")]

extern "C" {
  // Объявляем внешнюю функцию из libc
  fn puts(s: *const u8) -> i32;
}

#[start] // Говорим, что выполнение надо начинать с этого символа
fn main(_argc: isize, _argv: *const *const u8) -> isize {
  unsafe {
    // В Расте строки не нуль-терминированные
    puts("Hello, world!\0".as_ptr());
  }
  return 0;
}

#[panic_handler] // Удовлетворяем компилятор
fn panic(_panic: &core::panic::PanicInfo<'_>) -> ! {
  loop {}
}

#[lang = "eh_personality"] // Удовлетворяем компилятор
extern "C" fn eh_personality() {}

Так как здесь и далее код требует нестабильных фич, советую воткнуть в корень проекта файлик, который будет управлять версией компилятора:


$ cat rust-toolchain.toml 
[toolchain]
channel = "nightly-2022-06-09"

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


А в Cargo.toml добавить отключение размотки, все равно она в коде нигде не будет использоваться:


[profile.dev]
panic = "abort"

[profile.release]
panic = "abort"

Для этого хэлло ворлда cargo-expand покажет следующее:


#[prelude_import]
use core::prelude::rust_2021::*;
#[macro_use]
extern crate core;
...

То есть компилятор неявно вставил импорт библиотеки core (extern crate core;) и прелюдию core (use core::prelude::rust_2021::*;).


Ниже представлена сводная таблица, описывающая разницу между core и std.


Функциональность core std
динамическое выделение памяти нет *1 да
коллекции (Vec, HashMap и т.д.) нет *2 да
доступ к std нет да
доступ к core да да
низкоуровневая разработка да нет

  1. да, если используется крейт alloc и настроен глобальный аллокатор;
  2. да, если коллекции тоже #![no_std] и зависят от core.

Большинство структур и типажей стандартной библиотеки описываются именно в core, а не в std:


  • Методы примитивов bool, i32...;
  • Типы Range, Option, Result, Cell, RefCell, PhantomData...;
  • Типажи Hash, Drop, Debug, Iterator, Future, Unpin...;
  • Функции forget, drop, swap...


Отключаем core


Мы не ищем лёгких путей, поэтому мы отключим и std, и core с помощью атрибута #![no_core]. Такая функциональность по разным оценкам требуется от 3577 до 4518 людям в мире на момент написания статьи (именно столько людей контрибутят в компилятор Раста, но github даёт одни цифры, а git log --format="%an" | sort -u | wc -l другие). Вы же не думаете, что я тут беру статистику с потолка?


#![feature(no_core)]
#![feature(lang_items)]

#![no_core]

// Говорим компилятору влинковать libc
#[cfg(target_os = "linux")]
#[link(name = "c")]
extern {}

// Функция `main` на самом деле не точка входа, а вот `start` - да.
#[lang = "start"]
fn start<T>(_main: fn() -> T, _argc: isize, _argv: *const *const u8) -> isize {
  42
}

// Втыкаем символ, чтобы не получить ошибку undefined reference to `main'
fn main() { }

// Нужно компилятору
#[lang = "sized"]
pub trait Sized {}

Проверить работоспособность можно только по коду возврата: echo $? должен вернуть 42.


Мы почти добрались до самого низа. У нас нет возможности складывать числа, если попробовать их сложить, будет ошибка:


error[E0369]: cannot add `{integer}` to `{integer}`
  --> src/main.rs:14:8
   |
14 |     40 + 2
   |     -- ^ - {integer}
   |     |
   |     {integer}

Да ничего у нас нет, только определение примитивов i8, usize, str, но работать с ними нельзя.



Отключаем crt


Rust компилирует объектные файлы самостоятельно, но использует внешний (обычно это системный) линковщик. По умолчанию линковщик добавляет *crt*.o, в которых определяется стартовый символ (_start), но этот символ можно переопределить. Для этого отключаем сишный рантайм:


$ cargo rustc -- -C link-args=-nostartfiles

Или с помощью конфига в корне проекта можно задать флаги линковки:


$ cat .cargo/config 
[build]
rustflags = ["-C", "link-args=-nostartfiles"]

Тогда с .cargo/config и rust-toolchain.toml файлом сборка проекта осуществляется короткой командой cargo build. Ну или вы можете вбивать cargo +nightly-2022-06-09 rustc -- -C link-args=-nostartfiles.


Вид нашего хэлло ворлда приобретает форму:


#![feature(no_core)]
#![feature(lang_items)]
#![no_core]
#![no_main]

#[no_mangle]
extern "C" fn _start() {}

// Нужно компилятору
#[lang = "sized"]
pub trait Sized {}

Девственный ассемблер:


$ objdump -Cd ./target/debug/hello_world

./target/debug/hello_world:     file format elf64-x86-64

Disassembly of section .text:

0000000000001000 <_start>:
    1000:   c3                      retq

Компилируем и запускаем:


$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.00s
     Running `target/debug/hello_world`
Illegal instruction (core dumped)

Прекрасно. С этим можно начинать работать.



Пишем хэлло ворлд


Вообще до мейна происходит очень много интересного: инициализация статиков, профилировщика. Советую посмотреть доклад Мэтта Годболта:


https://www.youtube.com/watch?v=dOfucXtyEsU


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



Символ _start


Его мы будем писать на ассемблере. В std/core препроцессор ассемблерных вставок включается по умолчанию, а вот нам надо включить его явно.


#![feature(decl_macro)]
#![feature(rustc_attrs)]
#[rustc_builtin_macro]
pub macro asm("assembly template", $(operands,)* $(options($(option),*))?) {
  /* compiler built-in */
}

_start — это специальная функция, которой не требуется пролог и эпилог, поэтому ее надо пометить как naked.


#![feature(naked_functions)]

#[no_mangle]
#[naked]
unsafe extern "C" fn _start() {
  // Стырено из книги А.В. Столярова.
  // А, простите, там код под 32 бита, в книге 2021 года.
  // Значит, не стырено.
  asm!(
    "mov rdi, [rsp]", // argc
    "mov rax, rsp",
    "add rax, 8",
    "mov rsi, rax", // argv
    "call _start_main",
    options(noreturn),
  )
}

#[no_mangle]
extern "C" fn _start_main(argc: usize, argv: *const *const u8) -> isize {
  main(argc, argv);
  0
}

#[no_mangle]
fn main(_argc: usize, _argv: *const *const u8) -> isize {
  // И вот мы добрались до мейна
  return 0;
}

Компилируем и запускаем: Illegal instruction (core dumped). Я чую, что мы на правильном пути!



Сисколы


Всего нам понадобится два сискола: exit и write.


"Подложки" для сисколлов я хочу реализовать в общем виде, чтобы они принимали номер сисколла и аргументы (syscall1 — 1 аргумент, syscall3 — 3 аргумента).


man 2 syscall дает нам следующую информацию:


Architecture calling conventions
Every  architecture has its own way of invoking and passing arguments
to the kernel.  The details for various architectures are listed
in the two tables below.

The first table lists the instruction used to transition to kernel mode
(which might not be the fastest or best way to transition to the kernel,
so  you might have to refer to vdso(7)), the register used to indicate
the system call number, the register(s) used to return the system call
result, and the register used to signal an error.

Arch/ABI    Instruction           System  Ret  Ret  Error    Notes
                                  call #  val  val2
───────────────────────────────────────────────────────────────────
i386        int $0x80             eax     eax  edx  -
x86-64      syscall               rax     rax  rdx  -        5

The second table shows the registers used to pass the system call arguments.

Arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
──────────────────────────────────────────────────────────────
i386          ebx   ecx   edx   esi   edi   ebp   -
x86-64        rdi   rsi   rdx   r10   r8    r9    -


Завершение процесса


У данного системного вызова есть замечательное свойство — он никогда не возвращается. Этот факт можно использовать с помощью типов и интринзиков, чтобы дать понять компилятору, что любой код после данного сискола никогда не будет выполнен. Это реализуется через тип ! (never) и интринзик unreachable:


#![feature(intrinsics)] // подключаем фичу объявления интринзиков

extern "rust-intrinsic" {
  // Чтобы компилятор знал, что есть некоторый код, которого не достичь.
  // Например, весь код после exit()
  pub fn unreachable() -> !;
}

#[no_mangle]
extern "C" fn _start_main(argc: usize, argv: *const *const u8) -> ! {
  let status = main(argc, argv);
  exit(status);
}

#[inline(never)]
#[no_mangle]
// ! - это never type, компилятор понимает, что функция никогда не возвращается
fn exit(exit_code: i64) -> ! {
  unsafe {
    syscall1(60, exit_code);
    unreachable()
  }
}

#[inline(always)]
unsafe fn syscall1(n: i64, a1: i64) -> i64 {
  let ret: i64;
  asm!(
    "syscall",
    in("rax") n,
    in("rdi") a1,
    lateout("rax") ret,
  );
  ret
}

Если запустить получившийся бинарник, echo $? вернет ожидаемый 0.



Запись в файл


Настало время реализовать вывод "Hello, world!" в стандартный поток вывода! \<Не забыть изменить на менее глупую фразу перед публикацией>.


#[no_mangle]
fn main(_argc: usize, _argv: *const *const u8) -> i64 {
  let string = b"Hello, world!\n" as *const _ as *const u8;
  write(1, string, 14);
  return 0;
}

#[inline(never)]
#[no_mangle]
fn write(fd: i64, data: *const u8, len: i64) -> i64 {
  unsafe { syscall3(1, fd, data as i64, len) }
}

#[inline(always)]
unsafe fn syscall3(n: i64, a1: i64, a2: i64, a3: i64) -> i64 {
  let ret: i64;
  asm!(
    "syscall",
    in("rax") n,
    in("rdi") a1,
    in("rsi") a2,
    in("rdx") a3,
    lateout("rax") ret,
  );
  ret
}

Хелло ворлд на Расте под Linux x86_64 целиком
#![feature(no_core)]
#![feature(lang_items)]
#![no_core]
#![no_main]
#![feature(naked_functions)]
#![feature(decl_macro)]
#![feature(rustc_attrs)]
#![feature(intrinsics)]

// Нужно компилятору
#[lang = "sized"]
pub trait Sized {}

#[lang = "copy"]
pub trait Copy {}

impl Copy for i64 {} // Говорим компилятору, что объект этого типа можно копировать байт за байтом
impl Copy for usize {}

#[rustc_builtin_macro]
pub macro asm("assembly template", $(operands,)* $(options($(option),*))?) {
  /* compiler built-in */
}

extern "rust-intrinsic" {
  // Чтобы компилятор знал, что есть некоторый код, которого не достичь.
  // Например, весь код после exit()
  pub fn unreachable() -> !;
}

#[no_mangle]
#[naked]
unsafe extern "C" fn _start() {
  // Стырено из книги А.В. Столярова.
  // А, простите, там код под 32 бита, в книге 2021 года.
  // Значит, не стырено.
  asm!(
    "mov rdi, [rsp]", // argc
    "mov rax, rsp",
    "add rax, 8",
    "mov rsi, rax", // argv
    "call _start_main",
    options(noreturn),
  )
}

#[no_mangle]
extern "C" fn _start_main(argc: usize, argv: *const *const u8) -> ! {
  let status = main(argc, argv);
  exit(status);
}

#[no_mangle]
fn main(_argc: usize, _argv: *const *const u8) -> i64 {
  let string = b"Hello, world!\n" as *const _ as *const u8;
  write(1, string, 14);
  return 0;
}

#[inline(never)]
#[no_mangle]
// ! - это never type, компилятор понимает, что функция никогда не возвращается
fn exit(status: i64) -> ! {
  unsafe {
    syscall1(60, status);
    unreachable()
  }
}

#[inline(never)]
#[no_mangle]
fn write(fd: i64, data: *const u8, len: i64) -> i64 {
  unsafe { syscall3(1, fd, data as i64, len) }
}

#[inline(always)]
unsafe fn syscall1(n: i64, a1: i64) -> i64 {
  let ret: i64;
  asm!(
    "syscall",
    in("rax") n,
    in("rdi") a1,
    lateout("rax") ret,
  );
  ret
}

#[inline(always)]
unsafe fn syscall3(n: i64, a1: i64, a2: i64, a3: i64) -> i64 {
  let ret: i64;
  asm!(
    "syscall",
    in("rax") n,
    in("rdi") a1,
    in("rsi") a2,
    in("rdx") a3,
    lateout("rax") ret,
  );
  ret
}

Запускаем и проверяем:


$ cargo r
    Finished dev [unoptimized + debuginfo] target(s) in 0.01s
     Running `target/debug/hello_world`
Hello, world!
$ echo $?
0
$ strip ./target/debug/hello_world
$ stat -c %s ./target/debug/hello_world
13096

Оно работает! Но размер бинарника 13096 байт. Что ж, применим ld.lld-14:


$ cat .cargo/config 
[build]
rustflags = ["-C", "linker=ld.lld-14"]

$ cargo r
   Compiling hello_world v0.1.0 (/home/USER/rustmustdie/article/chapter_4)
    Finished dev [unoptimized + debuginfo] target(s) in 0.13s
     Running `target/debug/hello_world`
Hello, world!
$ echo $?
0
$ strip ./target/debug/hello_world
$ stat -c %s ./target/debug/hello_world
1712

Уии!


То есть нет =( Получилось 1712 байт против 1132 байт сишной реализации. Не забываем, что в сишной реализации вообще другой код, он хитрый, с непростым приветствием, то есть у него больше функциональность, но меньше размер.



Приводим к общему знаменателю


Вот было бы здорово, если бы у нас был:


  • Единый компилятор (gcc),
  • Единый линковщик (ld.lld-14),
  • Одни и те же флаги компиляции -Os -masm=intel -m32 -fno-pic -fno-asynchronous-unwind-tables,
  • Одни и те же флаги линковки --no-pie --no-dynamic-linker,
  • Да и код, выполняющий одну и ту же программу, не правда ли?
  • Чтобы был _start с прыжком в _start_main, который и будет вызывать функцию main,
  • Чтобы было два сискола sys_exit и sys_write (именование из книги Столярова),
  • Чтобы они были реализованы через обобщение сисколов syscall1 и syscall3.

Жаль, что все вместе это невозможно… Or is it?




Компилируем gcc и rustc_codegen_gcc


Архитектура компилятора rustc позволяет подключить не только бекенд llvm, но и gcc. Проект, который занимается поддержкой gcc, называется rustc_codegen_gcc. Конечно же не все так просто, с ним надо провести профекалтическую работу.


$ sudo apt install flex make gawk libgmp-dev libmpfr-dev libmpc-dev gcc-multilib

Клонируем rustc_codegen_gcc, патченный gcc и собираем gcc с поддержкой i386:


# У меня версия 1724042e228c3 от Wed Sep 14 09:22:50 2022
$ git clone https://github.com/rust-lang/rustc_codegen_gcc.git --depth 1
rustc_codegen_gcc$ cd rustc_codegen_gcc

#BUILD GCC (20 mins)
rustc_codegen_gcc$ git clone https://github.com/antoyo/gcc.git --depth 1
rustc_codegen_gcc$ cd gcc
rustc_codegen_gcc/gcc$ mkdir build install
rustc_codegen_gcc/gcc$ cd build
rustc_codegen_gcc/gcc/build$ ../configure --enable-host-shared --enable-languages=jit,c --disable-bootstrap --enable-multilib --target=x86_64-pc-linux-gnu --with-arch-32=i686 --with-abi=m64 --with-multilib-list=m32,m64 --enable-multiarch --prefix=$(pwd)/../install
rustc_codegen_gcc/gcc/build$ make -j8
rustc_codegen_gcc/gcc/build$ make install # в папочку ../install
rustc_codegen_gcc/gcc/build$ cd ../../
rustc_codegen_gcc$ echo $(pwd)/gcc/install/lib/ > gcc_path

Мастер ветка пока что не поддерживает i386 из коробки, но это можно исправить:


Патч rustc_codegen_gcc, чтобы заработал i386
diff --git a/config.sh b/config.sh
index b25e215..18574f2 100644
--- a/config.sh
+++ b/config.sh
@@ -20,8 +20,9 @@ else
 fi

 HOST_TRIPLE=$(rustc -vV | grep host | cut -d: -f2 | tr -d " ")
-TARGET_TRIPLE=$HOST_TRIPLE
+#TARGET_TRIPLE=$HOST_TRIPLE
 #TARGET_TRIPLE="m68k-unknown-linux-gnu"
+TARGET_TRIPLE="i686-unknown-linux-gnu"

 linker=''
 RUN_WRAPPER=''
@@ -33,6 +34,8 @@ if [[ "$HOST_TRIPLE" != "$TARGET_TRIPLE" ]]; then
       # We are cross-compiling for aarch64. Use the correct linker and run tests in qemu.
       linker='-Clinker=aarch64-linux-gnu-gcc'
       RUN_WRAPPER='qemu-aarch64 -L /usr/aarch64-linux-gnu'
+   elif [[ "$TARGET_TRIPLE" == "i686-unknown-linux-gnu" ]]; then
+      : # do nothing
    else
       echo "Unknown non-native platform"
    fi
diff --git a/src/back/write.rs b/src/back/write.rs
index efcf18d..e640fbe 100644
--- a/src/back/write.rs
+++ b/src/back/write.rs
@@ -14,6 +14,8 @@ pub(crate) unsafe fn codegen(cgcx: &CodegenContext<GccCodegenBackend>, _diag_han
     let _timer = cgcx.prof.generic_activity_with_arg("LLVM_module_codegen", &*module.name);
     {
         let context = &module.module_llvm.context;
+        context.add_command_line_option("-m32");
+        context.add_driver_option("-m32");

         let module_name = module.name.clone();
         let module_name = Some(&module_name[..]);
diff --git a/src/base.rs b/src/base.rs
index 8cc9581..fb8bd88 100644
--- a/src/base.rs
+++ b/src/base.rs
@@ -98,7 +98,7 @@ pub fn compile_codegen_unit<'tcx>(tcx: TyCtxt<'tcx>, cgu_name: Symbol, supports_
         context.add_command_line_option("-mpclmul");
         context.add_command_line_option("-mfma");
         context.add_command_line_option("-mfma4");
-        context.add_command_line_option("-m64");
+        context.add_command_line_option("-m32");
         context.add_command_line_option("-mbmi");
         context.add_command_line_option("-mgfni");
         context.add_command_line_option("-mavxvnni");
diff --git a/src/context.rs b/src/context.rs
index 2699559..056352a 100644
--- a/src/context.rs
+++ b/src/context.rs
@@ -161,13 +161,13 @@ impl<'gcc, 'tcx> CodegenCx<'gcc, 'tcx> {
         let ulonglong_type = context.new_c_type(CType::ULongLong);
         let sizet_type = context.new_c_type(CType::SizeT);

-        let isize_type = context.new_c_type(CType::LongLong);
-        let usize_type = context.new_c_type(CType::ULongLong);
+        let isize_type = context.new_c_type(CType::Int);
+        let usize_type = context.new_c_type(CType::UInt);
         let bool_type = context.new_type::<bool>();

         // TODO(antoyo): only have those assertions on x86_64.
-        assert_eq!(isize_type.get_size(), i64_type.get_size());
-        assert_eq!(usize_type.get_size(), u64_type.get_size());
+        assert_eq!(isize_type.get_size(), i32_type.get_size());
+        assert_eq!(usize_type.get_size(), u32_type.get_size());

         let mut functions = FxHashMap::default();
         let builtins = [
diff --git a/src/int.rs b/src/int.rs
index 0c5dab0..5fd4925 100644
--- a/src/int.rs
+++ b/src/int.rs
@@ -524,7 +524,7 @@ impl<'a, 'gcc, 'tcx> Builder<'a, 'gcc, 'tcx> {
         // when having proper sized integer types.
         let param_type = bswap.get_param(0).to_rvalue().get_type();
         if param_type != arg_type {
-            arg = self.bitcast(arg, param_type);
+            arg = self.cx.context.new_cast(None, arg, param_type);
         }
         self.cx.context.new_call(None, bswap, &[arg])
     }
diff --git a/src/lib.rs b/src/lib.rs
index e43ee5c..8fb5823 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -104,6 +104,7 @@ impl CodegenBackend for GccCodegenBackend {
         let temp_dir = TempDir::new().expect("cannot create temporary directory");
         let temp_file = temp_dir.into_path().join("result.asm");
         let check_context = Context::default();
+        check_context.add_command_line_option("-m32");
         check_context.set_print_errors_to_stderr(false);
         let _int128_ty = check_context.new_c_type(CType::UInt128t);
         // NOTE: we cannot just call compile() as this would require other files than libgccjit.so.

И поверх этого патча надо применить еще один, чтобы libgccjit.so компилировал только с нужным набором флагов:


Патч rustc_codegen_gcc для унификации флагов
diff --git a/src/base.rs b/src/base.rs
index fb8bd88..d5268dc 100644
--- a/src/base.rs
+++ b/src/base.rs
@@ -87,29 +87,11 @@ pub fn compile_codegen_unit<'tcx>(tcx: TyCtxt<'tcx>, cgu_name: Symbol, supports_
         // Instantiate monomorphizations without filling out definitions yet...
         //let llvm_module = ModuleLlvm::new(tcx, &cgu_name.as_str());
         let context = Context::default();
-        // TODO(antoyo): only set on x86 platforms.
         context.add_command_line_option("-masm=intel");
-        // TODO(antoyo): only add the following cli argument if the feature is supported.
-        context.add_command_line_option("-msse2");
-        context.add_command_line_option("-mavx2");
-        // FIXME(antoyo): the following causes an illegal instruction on vmovdqu64 in std_example on my CPU.
-        // Only add if the CPU supports it.
-        context.add_command_line_option("-msha");
-        context.add_command_line_option("-mpclmul");
-        context.add_command_line_option("-mfma");
-        context.add_command_line_option("-mfma4");
         context.add_command_line_option("-m32");
-        context.add_command_line_option("-mbmi");
-        context.add_command_line_option("-mgfni");
-        context.add_command_line_option("-mavxvnni");
-        context.add_command_line_option("-mf16c");
-        context.add_command_line_option("-maes");
-        context.add_command_line_option("-mxsavec");
-        context.add_command_line_option("-mbmi2");
-        context.add_command_line_option("-mrtm");
-        context.add_command_line_option("-mvaes");
-        context.add_command_line_option("-mvpclmulqdq");
-        context.add_command_line_option("-mavx");
+        context.add_command_line_option("-fno-pic");
+        context.add_command_line_option("-fno-asynchronous-unwind-tables");
+        context.add_command_line_option("-Os");

         for arg in &tcx.sess.opts.cg.llvm_args {
             context.add_command_line_option(arg);

Клонируем llvm и собираем rustc_codegen_gcc:


#BUILD RUSTC: (5 mins)
rustc_codegen_gcc$ git clone https://github.com/llvm/llvm-project llvm --depth 1 --single-branch
rustc_codegen_gcc$ export RUST_COMPILER_RT_ROOT="$PWD/llvm/compiler-rt"
rustc_codegen_gcc$ ./prepare_build.sh # download and patch sysroot src
rustc_codegen_gcc$ ./build.sh

Всё, теперь у нас есть собранный своими ручками компилятор Си (~/rustc_codegen_gcc/gcc/install/bin/gcc), libgccjit.so для компиляции Раста c захардкоженными флагами -Os -masm=intel -m32 -fno-pic -fno-asynchronous-unwind-tables и скрипт ~/rustc_codegen_gcc/cargo.sh, который подсовывает фронтенду rustc бекенд gcc.



Хэлло ворлд на Си под i386


Код
int sys_write(int fd, const void *buf, int size);
void sys_exit(int status);
static int main(int argc, char **argv);
static int syscall1(int n, int a1);
static int syscall3(int n, int a1, int a2, int a3);

static const char hello[] = "Hello, world!\n";

void _Noreturn __attribute__((naked)) _start() {
  __asm volatile (
    "_start:\n"
    "  mov ecx, [esp]\n"
    "  mov eax, esp\n"
    "  add eax, 4\n"
    "  push eax\n"
    "  push ecx\n"
    "  call _start_main\n"
  );
}

void _Noreturn _start_main(int argc, char **argv) {
  int status = main(argc, argv);
  sys_exit(status);
}

static int main(int argc, char **argv)
{
  sys_write(1, hello, sizeof(hello)-1);
  return 0;
}

void _Noreturn __attribute__ ((noinline)) sys_exit(int status) {
  syscall1(1, status);
  __builtin_unreachable();
}

int __attribute__ ((noinline)) sys_write(int fd, const void *buf, int size) {
  return syscall3(4, fd, (int) buf, size);
}

static int syscall1(int n, int a1) {
  int ret;
  __asm volatile (
    "  int 0x80"
    : "=a" (ret)
    : "0" (n), "b" (a1)
    : "memory"
  );
  return ret;
}

static int syscall3(int n, int a1, int a2, int a3) {
  int ret;
  __asm volatile (
    "  int 0x80"
    : "=a" (ret)
    : "0" (n), "b" (a1), "c" (a2), "d" (a3)
    : "memory"
  );
  return ret;
}

Все эти приседания с _Noreturn, static, __attribute__((naked)) прямое отражение того, что было в коде на Расте. Т.е. говорим компилятору, что из sys_exit нельзя выйти, static — для красивого инлайна (и чтобы в итоговом бинаре отсутствовал такой символ), а __attribute__((naked)) — чтобы компилятор не вставил пролог и эпилог для _start.


Сборка:


~/rustc_codegen_gcc/gcc/install/bin/gcc -Os -masm=intel -m32 -fno-pic -fno-asynchronous-unwind-tables -Wall -Wno-main -c hello_world.c
ld.lld-14 --no-pie --no-dynamic-linker hello_world.o -o hello_world
strip hello_world
objcopy -j.text -j.rodata hello_world

Проверяем:


$ ./build.sh
$ ./hello_world 
Hello, world!


Хэлло ворлд на Расте под i386


Код
#![feature(no_core)]
#![feature(lang_items)]
#![feature(naked_functions)]
#![feature(decl_macro)]
#![feature(rustc_attrs)]
#![feature(intrinsics)]
#![no_core]
#![no_main]

#[lang = "sized"]
pub trait Sized {}

#[lang = "copy"]
pub trait Copy {}

impl Copy for i32 {}
impl Copy for usize {}

#[rustc_builtin_macro]
pub macro asm("assembly template", $(operands,)* $(options($(option),*))?) {
  /* compiler built-in */
}

extern "rust-intrinsic" {
  pub fn unreachable() -> !;
}

#[no_mangle]
#[naked]
unsafe extern "C" fn _start() {
  asm!(
    "mov ecx, [esp]",
    "mov eax, esp",
    "add eax, 4",
    "push eax",
    "push ecx",
    "call _start_main",
    options(noreturn),
  )
}

#[no_mangle]
extern "C" fn _start_main(argc: usize, argv: *const *const u8) -> ! {
  let status = main(argc, argv);
  sys_exit(status);
}

#[no_mangle]
fn main(_argc: usize, _argv: *const *const u8) -> i32 {
  let string = b"Hello, world!\n" as *const _ as *const u8;
  sys_write(1, string, 14);
  return 0;
}

#[inline(never)]
#[no_mangle]
fn sys_write(fd: i32, data: *const u8, len: i32) -> i32 {
  unsafe { syscall3(4, fd, data as _, len) }
}

#[inline(never)]
#[no_mangle]
fn sys_exit(status: i32) -> ! {
  unsafe {
    syscall1(1, status);
    unreachable()
  }
}

#[inline(always)]
unsafe extern "C" fn syscall1(n: i32, a1: i32) -> i32 {
  let ret: i32;
  asm!(
    "int 0x80",
    in("eax") n,
    in("ebx") a1,
    lateout("eax") ret,
  );
  ret
}

#[inline(always)]
unsafe fn syscall3(n: i32, a1: i32, a2: i32, a3: i32) -> i32 {
  let ret: i32;
  asm!(
    "int 0x80",
    in("eax") n,
    in("ebx") a1,
    in("ecx") a2,
    in("edx") a3,
    lateout("eax") ret,
  );
  ret
}

Сборка:


# cargo.sh, предоставляемый rustc_codegen_gcc, принимает только переменную окружения CG_RUSTFLAGS
# поэтому в .cargo/config эти переменные не установить. Увы. 
export CG_RUSTFLAGS="-C linker=ld.lld-14 -C link-args=--no-pie -C link-args=--no-dynamic-linker"
~/rustc_codegen_gcc/cargo.sh b --target i686-unknown-linux-gnu
strip ./target/i686-unknown-linux-gnu/debug/hello_world
objcopy -j.text -j.rodata ./target/i686-unknown-linux-gnu/debug/hello_world

Проверяем:


$ ./build.sh 
rustc_codegen_gcc is build for rustc 1.65.0-nightly (748038961 2022-08-25) but the default rustc version is rustc 1.63.0-nightly (7466d5492 2022-06-08).
Using rustc 1.65.0-nightly (748038961 2022-08-25).
   Compiling hello_world v0.1.0 (/home/USER/rustmustdie/article/chapter_6)
    Finished dev [unoptimized + debuginfo] target(s) in 0.84s
$ ./target/i686-unknown-linux-gnu/debug/hello_world 
Hello, world!


Сравнение


Си Раст
$ stat -c %s hello_world
496
$ size -A hello_world
hello_world  :
section   size      addr
.rodata     15   4194516
.text       84   4198627
Total       99


$ stat -c %s ./hello_world
464
$ size -A ./hello_world
./hello_world  :
section   size      addr
.rodata     14   4194484
.text       82   4198594
Total       96



Вот так, размер файла на Расте получился 464 байта, а на Си — 494 байт. Предлагаю читателю самостоятельно ответить на вопрос, обладает ли Раст свойством абсолютной независимости от библиотечного кода, также иногда называемым zero runtime.


Для интересующихся, вот вся инфа о бинарях:


Си
$ objdump -Cd hello_world

hello_world:     file format elf32-i386

Disassembly of section .text:

004010e3 <_start>:
  4010e3:   8b 0c 24                mov    (%esp),%ecx
  4010e6:   89 e0                   mov    %esp,%eax
  4010e8:   83 c0 04                add    $0x4,%eax
  4010eb:   50                      push   %eax
  4010ec:   51                      push   %ecx
  4010ed:   e8 27 00 00 00          call   401119 <_start_main>
  4010f2:   0f 0b                   ud2    

004010f4 <sys_exit>:
  4010f4:   55                      push   %ebp
  4010f5:   b8 01 00 00 00          mov    $0x1,%eax
  4010fa:   89 e5                   mov    %esp,%ebp
  4010fc:   53                      push   %ebx
  4010fd:   8b 5d 08                mov    0x8(%ebp),%ebx
  401100:   cd 80                   int    $0x80

00401102 <sys_write>:
  401102:   55                      push   %ebp
  401103:   b8 04 00 00 00          mov    $0x4,%eax
  401108:   89 e5                   mov    %esp,%ebp
  40110a:   53                      push   %ebx
  40110b:   8b 4d 0c                mov    0xc(%ebp),%ecx
  40110e:   8b 55 10                mov    0x10(%ebp),%edx
  401111:   8b 5d 08                mov    0x8(%ebp),%ebx
  401114:   cd 80                   int    $0x80
  401116:   5b                      pop    %ebx
  401117:   5d                      pop    %ebp
  401118:   c3                      ret    

00401119 <_start_main>:
  401119:   55                      push   %ebp
  40111a:   89 e5                   mov    %esp,%ebp
  40111c:   83 ec 0c                sub    $0xc,%esp
  40111f:   6a 0e                   push   $0xe
  401121:   68 d4 00 40 00          push   $0x4000d4
  401126:   6a 01                   push   $0x1
  401128:   e8 d5 ff ff ff          call   401102 <sys_write>
  40112d:   31 c0                   xor    %eax,%eax
  40112f:   89 04 24                mov    %eax,(%esp)
  401132:   e8 bd ff ff ff          call   4010f4 <sys_exit>

$ readelf -a hello_world
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x4010e3
  Start of program headers:          52 (bytes into file)
  Start of section headers:          336 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         4
  Size of section headers:           40 (bytes)
  Number of section headers:         4
  Section header string table index: 3

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .rodata           PROGBITS        004000d4 0000d4 00000f 00   A  0   0  4
  [ 2] .text             PROGBITS        004010e3 0000e3 000054 00  AX  0   0  1
  [ 3] .shstrtab         STRTAB          00000000 000137 000019 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00400034 0x00400034 0x00080 0x00080 R   0x4
  LOAD           0x000000 0x00400000 0x00400000 0x000e3 0x000e3 R   0x1000
  LOAD           0x0000e3 0x004010e3 0x004010e3 0x00054 0x00054 R E 0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .rodata 
   02     .text 
   03     

$ objdump -s ./hello_world

./hello_world:     file format elf32-i386

Contents of section .rodata:
 4000d4 48656c6c 6f2c2077 6f726c64 210a00    Hello, world!.. 
Contents of section .text:
 4010e3 8b0c2489 e083c004 5051e827 0000000f  ..$.....PQ.''....
 4010f3 0b55b801 00000089 e5538b5d 08cd8055  .U.......S.]...U
 401103 b8040000 0089e553 8b4d0c8b 55108b5d  .......S.M..U..]
 401113 08cd805b 5dc35589 e583ec0c 6a0e68d4  ...[].U.....j.h.
 401123 0040006a 01e8d5ff ffff31c0 890424e8  .@.j......1...$.
 401133 bdffffff                             ....               

Раст
$ objdump -Cd ./target/i686-unknown-linux-gnu/debug/hello_world

./target/i686-unknown-linux-gnu/debug/hello_world:     file format elf32-i386

Disassembly of section .text:

004010c2 <_start>:
  4010c2:   8b 0c 24                mov    (%esp),%ecx
  4010c5:   89 e0                   mov    %esp,%eax
  4010c7:   83 c0 04                add    $0x4,%eax
  4010ca:   50                      push   %eax
  4010cb:   51                      push   %ecx
  4010cc:   e8 25 00 00 00          call   4010f6 <_start_main>

004010d1 <sys_write>:
  4010d1:   55                      push   %ebp
  4010d2:   b8 04 00 00 00          mov    $0x4,%eax
  4010d7:   89 e5                   mov    %esp,%ebp
  4010d9:   53                      push   %ebx
  4010da:   8b 5d 08                mov    0x8(%ebp),%ebx
  4010dd:   8b 4d 0c                mov    0xc(%ebp),%ecx
  4010e0:   8b 55 10                mov    0x10(%ebp),%edx
  4010e3:   cd 80                   int    $0x80
  4010e5:   5b                      pop    %ebx
  4010e6:   5d                      pop    %ebp
  4010e7:   c3                      ret    

004010e8 <sys_exit>:
  4010e8:   55                      push   %ebp
  4010e9:   b8 01 00 00 00          mov    $0x1,%eax
  4010ee:   89 e5                   mov    %esp,%ebp
  4010f0:   53                      push   %ebx
  4010f1:   8b 5d 08                mov    0x8(%ebp),%ebx
  4010f4:   cd 80                   int    $0x80

004010f6 <_start_main>:
  4010f6:   55                      push   %ebp
  4010f7:   89 e5                   mov    %esp,%ebp
  4010f9:   83 ec 0c                sub    $0xc,%esp
  4010fc:   6a 0e                   push   $0xe
  4010fe:   68 b4 00 40 00          push   $0x4000b4
  401103:   6a 01                   push   $0x1
  401105:   e8 c7 ff ff ff          call   4010d1 <sys_write>
  40110a:   31 c0                   xor    %eax,%eax
  40110c:   89 04 24                mov    %eax,(%esp)
  40110f:   e8 d4 ff ff ff          call   4010e8 <sys_exit>

$ readelf -a ./target/i686-unknown-linux-gnu/debug/hello_world
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF32
  Data:                              2s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x4010c2
  Start of program headers:          52 (bytes into file)
  Start of section headers:          304 (bytes into file)
  Flags:                             0x0
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         4
  Size of section headers:           40 (bytes)
  Number of section headers:         4
  Section header string table index: 3

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .rodata           PROGBITS        004000b4 0000b4 00000e 00   A  0   0  4
  [ 2] .text             PROGBITS        004010c2 0000c2 000052 00  AX  0   0  1
  [ 3] .shstrtab         STRTAB          00000000 000114 000019 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
  L (link order), O (extra OS processing required), G (group), T (TLS),
  C (compressed), x (unknown), o (OS specific), E (exclude),
  p (processor specific)

There are no section groups in this file.

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  PHDR           0x000034 0x00400034 0x00400034 0x00080 0x00080 R   0x4
  LOAD           0x000000 0x00400000 0x00400000 0x000c2 0x000c2 R   0x1000
  LOAD           0x0000c2 0x004010c2 0x004010c2 0x00052 0x00052 R E 0x1000
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0

 Section to Segment mapping:
  Segment Sections...
   00     
   01     .rodata 
   02     .text 
   03     

$ objdump -s ./target/i686-unknown-linux-gnu/debug/hello_world

./target/i686-unknown-linux-gnu/debug/hello_world:     file format elf32-i386

Contents of section .rodata:
 4000b4 48656c6c 6f2c2077 6f726c64 210a      Hello, world!.  
Contents of section .text:
 4010c2 8b0c2489 e083c004 5051e825 00000055  ..$.....PQ.%...U
 4010d2 b8040000 0089e553 8b5d088b 4d0c8b55  .......S.]..M..U
 4010e2 10cd805b 5dc355b8 01000000 89e5538b  ...[].U.......S.
 4010f2 5d08cd80 5589e583 ec0c6a0e 68b40040  ]...U.....j.h..@
 401102 006a01e8 c7ffffff 31c08904 24e8d4ff  .j......1...$...
 401112 ffff                                 ..              

Сишная версия толще на одну инструкцию ud2 (занимает 2 байта) и на один нуль в конце строки. В sys_exit аргументы пушатся в разном порядке, а в бинарях в целом символы находятся по разным адресам, а так бинари абсолютно идентичны.


Полученный результат стал возможен благодаря автору rustc_codegen_gcc — Antoyo. Он ведет блог, в котором периодически репортит о прогрессе данного проекта. И прогресс действительно поражает воображение. Пользуясь моментом, я прошу вас запатреонить Antoyo или проспонсировать его на гитхабе. Он делает важное дело не только для языка Раст, но и для проекта gcc (улучшает libgccjit.so), что позволит в будущем отвязаться от llvm и, например, компилировать модули ядра Линукса под все доступные gcc платформы.



Выводы


Именно это свойство — zero runtime — делает Си единственным и безальтернативным кандидатом на роль языка для реализации ядер операционных систем и прошивок для микроконтроллеров. Тем удивительнее, насколько мало людей в мире этот момент осознают; и стократ удивительнее то, что людей, понимающих это, судя по всему, вообще нет среди членов комитетов по стандартизации (языка Си)…

— А. В. Столяров

Спасибо, буду знать.


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


Вся эта история с доцентом, студентом МГУ и статьёй https://rustmustdie.com/ показывает, что где-то внутри вуза построен странный образовательный процесс, который мешает студентам получать актуальную информацию и формировать независимое мнение.


Я бы мог пройтись практически по всем пунктам студента: толстые бинари, медленный код, сборщик мусора, что времена жизни — это хак компилятора, что заимствования слабее указателей. Но это бы лишний раз показало, что преподаватель, который влил эту чушь в голову студентам МГУ, не просто находится не на своем месте, он вообще ничего не смыслит в системном программировании.


Я бы хотел, чтобы в МГУ (самом МГУ!) ученые и студенты были открыты к познанию. Ведь в этом и есть суть университетов, нет? Слишком многого хочу?..



Ссылки на код


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


Если у вас возник вопрос, а как же реализовать сложение чисел в среде без рантайма, вот ссылка на Linux x86_64 проект с минимальной реализацией арифметики, адресной арифметики, базовых операций вроде взятия размера слайса, ссылки на данные толстого указателя и т.д., благодаря чему в хэлло ворлде вычисляется размер строки, а не подставляется магическое число:


#[no_mangle]
fn main() {
  print("Hello world!\n");
}

fn print(string: &str) {
  unsafe {
    write(1, string.as_ptr(), string.len())
  };
}

Комментарии (149)


  1. eoanermine
    30.09.2022 18:00
    +61

    Писать огромное количество кода только ради того, чтобы смочь сложить два и два без лишнего рантайма — это, конечно, прикольно.


    1. NickSin
      30.09.2022 18:11
      +49

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

      Автору респект за "вызов" было интересно читать.


      1. Didimus
        01.10.2022 17:43
        +5

        В двоичных кодах два на два умножить не так уж много текста надо


        1. sim31r
          01.10.2022 19:15
          +2

          Операционная система не пустит к процессору с минимальным текстом. На микроконтроллере да, 2-3 байта и весь код.


          1. WASD1
            02.10.2022 13:24
            +6

            Не понял почему человека замунусовали?

            Он немного кричаще выразился - но суть-то что соблюсти OS API по вызову нового процесса (формат elf или exe или чего ещё) и это не 4 байта в итоге - верна.


  1. ZaMaZaN4iK
    30.09.2022 18:12
    -80

    терминально бесполезный мусор.

    мне же приходится бороться с мусорной пропагандой

    на это ловятся все бездарные тупорылые дошколята, которые нажрались пропаганды и пошли блеять. Т.е. ЦА раста является дошколятский биомусор, который код писать не может

    почему ЦА говнораста - это веб-обезьяны и бездарные докшоялта? Почему всё, что пишется - это всякая дристня, которую пишут на жс/дристоне/другой скриптухе?

    ещё раз. Какой может быть с тобою "по делу", если ты жертва пропаганды? Ты не можешь существовать вне

    в любом случае молодец

    Зная вышеперечисленные факты (и многие другие), сложно воспринимать сравнение так называемого языка программирования Rust с чем-то нормальным всеръёз. Для более подробного и глубокого обсуждения рекомендую присоедениться к чату: https://t.me/proriv_zaparti2


    1. DrMefistO
      30.09.2022 21:32

      Эмоционально!


    1. PanDubls
      30.09.2022 21:52

      del


    1. indestructable
      03.10.2022 11:05
      +8

      Удивляет, что у некоторых людей присутствует иррациональная ненависть к Расту. Я так понимаю, в основном это сишники и плюсеры. Казалось бы, не нравится - проходи мимо. Аналогичное явление я наблюдал у линуксоидов, НЕНАВИДЯЩИХ виндоуз.


      1. F0iL
        03.10.2022 12:04
        +1

        Да ладно, это в обе стороны работает.
        Многие плюсовики интересуются Rust'ом и не прочь писать на нем.
        А у других нелюбовь к расту появляется из-за его очень назойливого сообщества. Тут на Хабре был отличный комментарий про это:

        Фанаты раста в тредах про с++ как веганы на барбекю пикнике. Орут громче всех, а кто вас звал то?


  1. QtRoS
    30.09.2022 18:17
    +52

    Я бы хотел, чтобы в МГУ (самом МГУ!) ученые и студенты были открыты к познанию. Ведь в этом и есть суть университетов, нет? Слишком многого хочу?..

    Не слишком, но давайте будем честными: ВУЗ, где преподаватели знают про Rust и даже могут подискутировать о нем, уже довольно хорош по сравнению со средним по больнице. В моем университете меня ругали за лишнюю переменную (память тратится типа) в коде. Или что процессоров по технологии ~32нм не может существовать в природе, потому что это меньше длина волны видимого света. Никто не запрещает подвергать сомнению слова преподавателя, перепроверять информацию, затем возвращаться к нему для обсуждения. В сомнении рождается истина (с)


    1. le2
      30.09.2022 18:27
      +32

      на ютубе есть лекция Жореса Алферова из которой можно узнать что он сам не знает что такое маркетинговые нанометры, он уверен что это размер транзистора.

      Лично я около трех раз ловил на экзамене преподов что они сами не понимают что-то по мелочам. К моему счастью они признавали ошибку. Но это нормально.


      1. WASD1
        30.09.2022 20:48
        +2

        Ну вообще "эффективная плотность упаковки 'неквадратных' элементов" - это как раз самая лучшая метрика, для такого понятия, как размер элемента.


      1. Halt
        01.10.2022 07:47
        +19

        Я бы сказал есть разница в знании терминов (особенно сиюминутных) и понимании принципов. Человек может не помнить, как называется та или иная хреновина, но понимать теорию, стоящую за ней. Ученому можно объяснить этот термин за 1 минуту, в то же время маркетологу, сыпящему выдуманными терминами и ничерта не понимающему в происходящем, хоть кол на голове теши, больше он понимать не станет, ибо не хватает именно фундаментальных знаний. Так что это не принципиально.


    1. yatanai
      01.10.2022 10:52
      +2

      Ну он в каком-то смысле прав, для планарных транзисторов меньше условных 24-28нм сделать нельзя. Но вот когда оно в тридэ, то можно сделать из этого 14нм, там же 2 транзистора на ячейку!


  1. le2
    30.09.2022 18:23
    +22

    Отличие явное. Си без стандартной библиотеки остается языком Си (то что происходит в ядре Линукса/UNIX которые собираются без всех зависимостей, в том числе и без стандартной библиотеки), а что останется от Раста без стандартной библиотеки? Будет ли это Растом?

    Ну и полное унижение для большинства программистов - а можете ли вы оперировать микросекундами на своём любимом языке? - это можно на ассемблерах и Си. В прочих языках всё недертминированное во времени придется вырезать и от любого языка останется нечто Си-подобное.
    Только не надо передергивать, я не говорю что Си это хорошо всегда и для всего.


    1. Halt
      30.09.2022 18:46
      +55

      а что останется от Раста без стандартной библиотеки? Будет ли это Растом?

      Да, будет. Автор статьи отломал только импорты. Сам язык от этого не перестал быть языком. Можно все так же использовать высокоуровневые абстракции, чем активно пользуются разработчики.

      Например, эмбеддеры пишут библиотеки, где на уровне типов нельзя выстрелить в ногу (хех) микроконтроллеру с неправильно сконфигурированными портами или переполнить стек. Разработчики операционных систем пишут абстракции конкуренции и доступа к ресурсам, не позволяющим их использовать неверно.

      Но они видимо Столярова не посещали и не знают, что это невозможно.

      Вообще, кому интересно рекомендую книгу Rust Embedded.


      1. victor_1212
        30.09.2022 21:10
        +2

        вмк всегда очень не равный состав преподавателей имел, люди которые пришли из промышленности (в те далекие времена, когда приходилось там бывать - Королев и др) это top notch, насколько знаю сильные люди там еще есть, но мало, и не Столяров, хотя против его книг ничего не имею, откровенного вранья там не замечал, просто по серьезному он не программист, а преподаватель скорее, хотя возможно сам думает иначе, так что все что он про rust пишет лучше не читать (imho как обычно)


      1. Stan88
        30.09.2022 23:30
        +5

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

        Хм...проверим статистику что у нас там по RTOS, драйверам и их поддержке:

        MOST POPULAR RTOS (2021)

        • Deos (DDC-I)

        • embOS (SEGGER)

        • FreeRTOS (Amazon)

        • Integrity (Green Hills Software)

        • Keil RTX (ARM)

        • LynxOS (Lynx Software Technologies)

        • MQX (Philips NXP / Freescale)

        • Nucleus (Mentor Graphics)

        • Neutrino (BlackBerry)

        • PikeOS (Sysgo)

        • SafeRTOS (Wittenstein)

        • ThreadX (Microsoft Express Logic)

        • µC/OS (Micrium)

        • VxWorks (Wind River)

        • Zephyr (Linux Foundation


        Ой, какая неожиданность - все они написаны на С/С++ (в большей степени на С). А как же так то...не надежно все это - сколько безногих разработчиков и промышленных систем.

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

        Ну а когда там появится RTOS на Rust, которая ложиться на большее кол-во популярных семейств МК, с паком необходимых драйверов, примерами под доступные dev-борды и хоть каким-то комьюнити - вот тогда можно будет потратить свое время и перейти на этот без сомнения хороший инструмент. Но пока что это экономически не обоснованно.


        1. F0iL
          30.09.2022 23:51
          +42

          пишут на очередном новом языке

          Rust за время своего существования уже в принципе доказал, что он не "очередной" :)

          Ой, какая неожиданность - все они написаны на С/С++ (в большей степени на С)

          Суровое наследие легаси, ну да, чему тут удивляться? Правда, при правильном подходе это не является фатальной проблемой - см. то же ядро Linux, которое уже 30 лет как разрабатывается на Си и у которого все API на Си, а теперь без двух минут как в нем можно писать драйвера на Rust, и Земля не остановилась :)


          1. Stan88
            01.10.2022 00:28
            +5

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

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


            1. Halt
              01.10.2022 07:55
              +18

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

              Чтобы понять почему, достаточно ответить на вопрос, почему Торвальдс не показал свой знаменитый средний палец Расту так как делал это C++ все 30 лет.

              Используя вашу логику можно сказать что и C++ никому не уперся, ибо серьезный бизнес уже 50 лет как использует COBOL. Но это уже, простите, демагогия.


              1. yatanai
                01.10.2022 11:03
                -5

                Хах, не понимаю претензий к крестам. Единственное что в нём плохо, что он не может определится для чего он.

                На подобии того, как в "низкоуровневое" языке появились СОМ интерфейсы, которые вообще-то крестовые Шарп пародии на самих себя. Как это воспринимать нормально?


                1. Ritan
                  01.10.2022 13:06
                  +12

                  Вы точно отличаете язык C++ от поделия MS?

                  Никакого COM в C++ нет и не было


        1. wertex
          01.10.2022 15:42
          +3

          Для Rust есть RTIC для Cortex-M. Довольно таки интересный аналог имеющихся RTOS.


          1. mayorovp
            01.10.2022 18:42
            +2

            Да, вот ссылка для тех кто не может этот rtic нагуглить: https://rtic.rs/1/book/en/


    1. 0xd34df00d
      30.09.2022 19:59
      +36

      Си без стандартной библиотеки остается языком Си

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


      (то что происходит в ядре Линукса/UNIX которые собираются без всех зависимостей, в том числе и без стандартной библиотеки),

      Тоже нет, потому что там используются фичи, не описанные стандартом С. Иными словами, существует удовлетворяющая стандарту реализация, которая не скомпилирует ядро линукса, или результат будет рабоотать не так, как ожидается.


      1. AnthonyMikh
        30.09.2022 20:02
        +11

        Иными словами, существует удовлетворяющая стандарту реализация, которая не скомпилирует ядро линукса

        Есть, называется clang :^)


        1. thevlad
          30.09.2022 23:05
          +1

          Нет, уже давно не называется https://www.kernel.org/doc/html/latest/kbuild/llvm.html


          1. AnthonyMikh
            01.10.2022 23:29
            +5

            При этом поддерживает далеко не все платформы, на которых собирается Linux, и для поддержки сборки которой потребовалось вносить патчи как в само ядро, так и в clang, для поддержки GNU-расширений.


            1. thevlad
              01.10.2022 23:40

              А какие не поддерживаются для которых есть бэкенд?


              1. AnthonyMikh
                02.10.2022 00:06
                +1

                Написано по ровно той ссылке, которую вы и привели (._. )


                1. thevlad
                  02.10.2022 00:12

                  Не увидел, там я вижу лишь список поддерживаемых платформ, и они все есть в бэкенде llvm. Соответственно мой вопрос: какие есть платформы из линукса, поддерживаемые бэкендом llvm, но которых нет в этом списке.


      1. thevlad
        01.10.2022 00:27

        Но существующие реализации некоторых компиляторов, собирают ядро на поддерживаевом им наборе архитектур. И тут вспоминается анекдот про математика и воздушный шар.


    1. checkpoint
      01.10.2022 12:04
      +6

      Только не надо передергивать, я не говорю что Си это хорошо всегда и для всего.

      Си - это хорошо всегда и для всего!


      1. mSnus
        02.10.2022 02:03
        +13

        Если пишешь ты на Си,

        Будь хоть трижды ламер -

        Каждый скажет о тебе:

        "Он крутой программер!"


  1. Halt
    30.09.2022 18:36
    +41

    Именно это свойство — zero runtime — делает Си единственным и
    безальтернативным кандидатом на роль языка для реализации ядер
    операционных систем и прошивок для микроконтроллеров.

    Очень мило. Особенно на фоне того, что буквально на днях у Лины заработал Linux драйвер графики для Apple M1, написанный на расте, а в ядро Linux вот-вот заедет его поддержка, одобренная самим Торвальдсом.

    Комментарии Лины по поводу разработки

    On the Rust side, I have to say I'm super pleased with my experience writing a driver like this in Rust! I've had zero concurrency issues (and the driver uses fine-grained locking, there's no big driver lock) - once single processes worked, running multiple apps concurrently just worked. Also zero memory leaks, dangling CPU or GPU pointers, use-after frees / free order mistakes, or anything like that! The only memory corruption issues I ran into were either fundamental mistakes in my unsafe DRM abstraction or core GPU memory management code, or happened from the GPU side (there's an issue with TLB invalidation, that's what the ugly workaround is for).

    I feel like Rust encourages good driver design and then the compiler goes a long way towards making the resulting code correct. All in all I didn't really have that many bugs to fix, mostly just logic issues (both because I'm new to DRM and because the GPU interface is all reverse engineered and we're still working out the details).

    The workaround for the GPU-side TLB inval issue has a large performance hit, but without that, kmscube does run at 1000+ FPS, and that's with a lot of suboptimal components that will be improved over time (e.g. my current allocator allocates/maps/unmaps/frees tons of little GPU structures per frame), so I'm also very optimistic about the performance aspect!

    The only major Rust issue I ran into is the lack of placement new, which I ended up working around with a very ugly place!() macro (it still has a soundness issue too, I need to fix it to drop things if initialization fails halfway through). Without that, I was quickly overflowing the kernel stacks (which is particularly ugly to debug without CONFIG VMAP STACK, which I didn't have set at first...). With the macro though, the stack frames are under control enough that there's no issue, but l'd really love to see core language support for this. I think it's really necessary for kernel/embedded development.

    Про эмбеддед я вообще молчу


    1. snuk182
      30.09.2022 19:38

      lack of placement new

      Ох, извечная боль...


  1. Politura
    30.09.2022 18:48
    +7

    Меня терзают смутные сомнения. Мне почему-то кажется, что вы писали не на расте, а на ассемблере, который компилируется растовским компилятором. Но если так, то почему вы это сравниваете с greet3.c из главы "Нулевой рантайм в Си" который написан исключительно на си, без каких-либо ассембленых вставок и миллионов указивок си перестать работать как си?


    1. Mingun
      30.09.2022 19:22
      +22

      "Нулевой рантайм в Си", написанный на Си, не работает. Чтобы он заработал, ему еще нужно 2 файла, которые не компилируются компилятором Си. Можете и на Расте функции, реализованные целиком в ассемблере, вытащить в отдельные файлы, либо на Си затащить их в файл Си (что и было сделано в конце).


      миллионов указивок си перестать работать как си?

      Если вы про атрибуты, то это нормальный код на Расте. Ничего не "перестает работать как раст", borrow checker не отключите, не надейтесь :)


      1. Politura
        30.09.2022 20:55
        +1

        Можете и на Расте функции, реализованные целиком в ассемблере, вытащить в отдельные файлы

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

        либо на Си затащить их в файл Си (что и было сделано в конце).

        Этот подход мне и не понравился. Там нет си вообще, там какая-то уродливая и нечитаемая фигня, зачем назвали это си? Сравните с изначальным вариантом, когда вынесли отдельно чистый ассемблер и отдельно - чистый и всем понятный си.


        1. thevlad
          30.09.2022 23:20
          +8

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


  1. aelaa
    30.09.2022 19:15
    +24

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


    1. maeris
      02.10.2022 02:53
      +6

      А кто это вообще?


      1. WASD1
        02.10.2022 13:26
        +4

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


      1. san-smith
        02.10.2022 20:15
        +8

        Адепт секты истинных программистов
        Единственным прибежищем программистов-любителей внезапно оказалась веб-разработка. К сожалению, начав в этой области, люди обычно ею же и заканчивают. Разницу между скриптами, составляющими веб-сайты, и серьёзными программами можно сравнить, пожалуй, с различием между мопедом и карьерным самосвалом; кроме того, привыкнув к «всепрощающему» стилю скриптовых языков типа того же PHP, большинство неофитов оказывается принципиально неспособно перейти к программированию на строгих языках типа Джавы или тем более Си, а хитросплетения Си++ для таких людей оказываются за горизонтом понимания. Веб-кодеры, как правило, получают неплохие деньги, не подозревая при этом, что такое настоящее программирование и что они для себя потеряли.
        © А.В. Столяров, Программирование: введение в профессию Стр. 15.


        и борцун с международными террористами
        За несколько лет, прошедших с момента предыдущего издания, мир несколько изменился: группа международных террористов, по недоразумению называющихся комитетом по стандартизации Си++, развернула весьма бурную и эффективную деятельность по окончательному уничтожению языка. Вышедшие последовательно «стандарты» С++11, С++14 и, наконец, С++17 не переставали удивлять публику: каждый раз казалось, что более мрачного и безумного извращения придумать уже нельзя, и каждый раз выход очередного «стандарта» наглядно демонстрировал, что всё возможно. Если под «языком Си++» понимать С++17, то о применении такого инструмента на практике не может быть никакой речи, т.е. с языком Си++ следует попращаться, устроить торжественные похороны и поискать альтернативу; впрочем, то же самое можно сказать про все его «стандарты», начиная с самого первого, принятого в 1998 году; строго говоря, язык Си++ как уникальное явление был уничтожен именно тогда.
        © А.В. Столяров, «Введение в язык СИ++», стр. 7


        1. F0iL
          02.10.2022 22:41
          +6

          Забористая трава у мужика, завидую.

          Студентов его, правда, жалко.


  1. Gorthauer87
    30.09.2022 19:29
    +2

    Зачем спорить с царём?


    1. AnthonyMikh
      30.09.2022 19:50
      +4

      Это не Царь. Царь анонимен, а Столяров — нет и до прямых оскорблений оппонентов вроде не опускается.


  1. 0xd34df00d
    30.09.2022 19:55
    +36

    Напомнило, как в 2008-м году на каком-то конкурсе для 11-классников какой-то чувак тоже из МГУ доказывал мне, что единственный способ написать быстрый программный комплекс — писать все на ассемблере. Видимо, к 2022-му году МГУшники смогли осилить подумали, что смогли осилить хотя бы С. Возможно, к 2050-му смогут и в раст.


    Я бы хотел, чтобы в МГУ (самом МГУ!)

    А что сам МГУ? Громкое имя, а на деле — пшик.


    1. omxela
      01.10.2022 14:36
      +2

      единственный способ написать быстрый программный комплекс — писать все на ассемблере

      Это Ваш пересказ слов чувака. Смысл такого утверждения туманен. Что такое "быстрый"? В сравнении с чем? Быстрота нужна для чего? И что такое загадочный "программный комплекс"? Я могу представить себе задачи, когда нужно считать каждую микросекунду (для чего нужен сторонний счётчик, кстати) и контролировать каждый поток буквально по тактам. В этом случае ассемблер - самый простой выбор для человека, которому нужен результат, а не понты и имитация понимания.

      Громкое имя, а на деле — пшик

      Подобное огульное утверждение вряд ли имеет смысл. Каждый "пшик" имеет имя и фамилию.


      1. 0xd34df00d
        02.10.2022 02:56
        +16

        Что такое "быстрый"? В сравнении с чем? Быстрота нужна для чего? И что такое загадочный "программный комплекс"?

        Расскажу историю чуть подробнее. В 2008-м году я был в 11-м классе и участвовал во всяких разных конкурсах, потому что они давали дополнительные ништяки при поступлении. Участвовал я с одним своим тогдашним проектом — софтиной на C++ с кутями, которая тогда фокусировалась на скачивании и обмене файлами через всякие HTTP, FTP, торренты и прочее, ну и там ещё были всякие вспомогательные фичи вокруг этой темы — вроде RSS (чтобы из лент автоматом торренты доставать, например), массовой загрузки по шаблону, и так далее. Ну и одна из основных фишек была в модульности, но неважно.


        Один из таких конкурсов, то ли при Бауманке, то ли при МИФИ, то ли при вообще какой-то компании (в духе microsoft imagine cup), не помню уже, имел стендовый формат — школьники стояли с плакатами, к ним подходили преподы/другие школьники/интересующиеся родители/етц и разговаривали. На моём плакате (ну как плакате, наборе листочков A4) было, среди прочего, сравнение моего поделия по CPU и RAM с прочими гуёвыми софтинами с аналогичной функциональностью — помню, azureus был (потому что его тормоза на моей тогдашней 512-мегабайтной машине вдохновили меня писать что-то своё), было что-то ещё.


        Охренеть, я даже фотку нашёл.


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


        Ну и хотя в среднем всем на моё поделие было, к счастью, плевать, ко мне таки подошла делегация из какого-то товарища из МГУ в летах и нескольких, возможно, аспирантов вокруг него, посмотрела на мой плакат, похмыкала, и товарищ в летах принялся доказывать, что это всё ерунда, софт на C++ заведомо не может быть нетребовательным к ресурсам (что бы это ни значило), и что единственный способ — писать на ассемблере. Да, не прошивку для контроллера, не кусок ядра ОС, а… да, кроссплатформенный торрент-клиент, например.


        На диалог товарищ настроен не был, несколько раз в разных формах повторил одну и ту же мысль, подытожил «нет, эффективный код возможен только на ас-сем-бле-ре!», и пошёл хмыкать над другими плакатами.


        В принципе, ровно в тот момент я окончательно убедился в правильности своего решения не тратить время на поступление в МГУ, будь то Мехмат или ВМК, так что чуваку, в принципе, спасибо.


        Я могу представить себе задачи, когда нужно считать каждую микросекунду (для чего нужен сторонний счётчик, кстати) и контролировать каждый поток буквально по тактам.

        На современном железе? Успехов с контролем по тактам на современных процессорах с OoO, кэшами (включая микроинструкции, TLB и вот это всё), и так далее. На многозадачной и нериалтаймовой ОС — тем более успехов.


        В этом случае ассемблер — самый простой выбор для человека, которому нужен результат

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


        а не понты и имитация понимания.

        Эта формулировка подразумевает неявное противопоставление каким-то другим инструментам, которые дают понты либо имитацию понимания. Можете уточнить, какие инструменты вы имели в виду?


        Подобное огульное утверждение вряд ли имеет смысл. Каждый "пшик" имеет имя и фамилию.

        Каждый «заборостроительный» тоже имеет имя и фамилию, но непонятно, что из этого следует.


      1. sshikov
        02.10.2022 13:10
        +3

        >когда нужно считать каждую микросекунду
        Я тоже могу представить себе задачи, когда речь идет о сотнях терабайтов данных в сутки, например. И никто микросекунды не считает, потому что цель программного комплекса, например, подготовить отчет для регулятора сегодня «к закрытию банковского дня». И это тоже программный комплекс, и тоже быстрый — но в своем понимании. И даже мысли писать что-то на C++ никому в голову не приходит, прошу заметить, не говоря уже про ассемблер.

        >Смысл такого утверждения туманен.
        Вот именно по этой причине. Чувак, который делает такие утверждения без уточнения, что такое быстрый и так далее — балабол. Я не согласен с вашим выводом про ассемблер, несколько по другой причине, нежели озвучил 0xd34df00d, а скорее по той, что очень редко требования по быстродействию относятся ко всей большой системе, И даже если вы пишете что-то на ассемблере, уже очень давно имеет смысл писать только самые критичные части, и только в тех случаях, когда ваш компилятор не умеет скажем использовать все возможности процессора. Все же остальное зачастую можно написать на том, на чем быстрее и дешевле. Потому что «нужен результат» как правило почему-то не когда попало, а вчера.


  1. RC_Cat
    30.09.2022 20:20
    +9

    для опровержения этих пунктов мне придется писать максимально уродские хэлло ворлды, которые только можно представить

    Погодите, и вы вправду не считаете что в этой фразе что-то нет так?


  1. valexey
    30.09.2022 20:35
    +8

    Этот пост — письменное изложение вот этого доклада с C++ Russia?


    1. bolk
      01.10.2022 18:43
      +8

      Возможно то, что обоих авторов зовут Роман не просто совпадение.


  1. Pastoral
    30.09.2022 21:06
    +11

    Я вроде как понимаю содержание этого замечательного цикла статей, но определённо и полностью не понимаю смысл.

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

    Отсюда вопрос: а что, обнаружилось что в некоторых кругах организована травля Rust? Если да, то чем она может быть вызвана и есть ли координация с Гугол ринувшимся делать Carbon чисто случайно как только Rust начал набирать популярность?


    1. 0xd34df00d
      30.09.2022 21:19
      +11

      Отсюда вопрос: а что, обнаружилось что в некоторых кругах организована травля Rust? Если да, то чем она может быть вызвана и есть ли координация с Гугол ринувшимся делать Carbon чисто случайно как только Rust начал набирать популярность?

      Ломающие новости! Выявлено спонсирование opennet'а гуглом!


    1. WASD1
      30.09.2022 22:55

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

      Почему наши российские учебники (книги этим тоже грешат, но меньше) по IT такие плохие - во всяком случае в худшую сторону отличаются от иностранных?

      Потому, что первый фильтр происходит по формальным критериям (в том числе корректности написанных утверждений: есть методика испытаний, есть 0.99999% производительности - так и пиши) и при их невыполнении девочка-секретарка (в лучшем случае аспиратнтка) напишет "не пущать".

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


      1. victor_1212
        01.10.2022 00:41

        > Почему наши российские учебники (книги этим тоже грешат, но меньше) по IT такие плохие

        в первую очередь интересны оригинальные книги написанные по собственному опыту, по чужому опыту это как правило компиляции (см книги Столярова), которые тоже могут быть полезны для обучения студентов, но не более


        1. victor_1212
          01.10.2022 02:56

          ps

          нетрудно привести примеры хороших оригинальных книг по программированию написанных по собственным работам, начиная с kernighan & ritchie, интересно спросить того кто карму минусовал за это сообщение -

          Вы можете привести контрпример, типа хорошей книги по программированию написанной по чужим работам (компиляции) кроме учебников конечно?


          1. 0xd34df00d
            01.10.2022 04:20
            +4

            Вы можете привести контрпример, типа хорошей книги по программированию написанной по чужим работам (компиляции) кроме учебников конечно?

            TAPL, например.


            1. victor_1212
              01.10.2022 17:41

              согласен, хорошо написано, и не компиляция конечно, Pierce phd делал в Carnegie Mellon, одна из лучших школ по CS, supercomputing center тоже там, кстати там тоже Gordon Bell работал (digital vp r&d, т.е. прямое отношение к созданию VAX), это к тому что в случае TAPL человек прямо в центре событий находился и сам активно работал, его thesis (Programming with Intersection Typesand Bounded Polymorphism) стоит взглянуть как пример хорошей теоретической работы, в моем понимании TAPL типа осмысление достигнутого в части языков, но в общем Вы правы

              ps

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


    1. F0iL
      30.09.2022 23:56
      +7

      и есть ли координация с Гугол ринувшимся делать Carbon чисто случайно как только Rust начал набирать популярность?

      Гугл говорит, что у Rust и Carbon какбэ разные цели и они не конкуренты:

      Existing modern languages already provide an excellent developer experience: Go, Swift, Kotlin, Rust, and many more. Developers that can use one of these existing languages should. Unfortunately, the designs of these languages present significant barriers to adoption and migration from C++. These barriers range from changes in the idiomatic design of software to performance overhead.

      Более того, Google один из основных спонсоров инициативы по поддержке разработки на Rust в ядре Linux.


      1. Pastoral
        01.10.2022 09:49
        -7

        Интереснейшая цитата возбудила моего внутреннего конспиролога того больше.

        Как Dart попал в and many more, это же предел мечтаний и свет (Истины) в окошке?

        Разве changes in the idiomatic design не должны попадать в категорию goals вместо barriers?

        Насколько я понимаю, получив отказ на просьбы пустить проприетарщину в ядро, Гугол теперь строит интерфейсы между собой и ядром Линукс, что должно дать Андроиду ядро посвежее и обновлений поболе и подешевле. Если бы Rust этому не способствовал, Гугол его в ядро не продвигал бы.

        «Наверно Rust есть хорошо и Гугол хочет его для себя» - подумал внутренний конспиролог.


    1. AnthonyMikh
      01.10.2022 23:37
      +6

      есть ли координация с Гугол ринувшимся делать Carbon чисто случайно как только Rust начал набирать популярность?

      В FAQ Carbon прямым текстом написано "If you can use Rust, ignore Carbon".


  1. WASD1
    30.09.2022 21:26
    +15

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

    А статью точно студент писал?
    Начал читать и в какой-то момент прям пахнуло нафталином из плохой методички по 30 лет никому не нужному предмету (назидательный тон и оторванные от реальности лет на 20 выкладки):

    То есть я понимаю, когда какой-нибудь дедушка в таком тоне пишет - он уже достиг того возраста, когда люди думают, что заню всё. Но блин студент... ну ему бы (чисто по причине опыта в программировании) ещё учиться и учиться, а он назидательную статью в журнал "вестник передовых работников кибернетики" строчит.


    1. ijsgaus
      30.09.2022 22:55
      +2

      Дедушек не надо обижать то уж.. Они не все еще нафталином пропахли :-)


    1. Racheengel
      30.09.2022 23:04
      -1

      Так это, чукча не читатель, чукча писатель..


    1. WraithOW
      01.10.2022 00:26
      +2

      Вы как будто в универе не учились. Сказано - пояснительная записка на 15 страниц, а у тебя только 8. Что делать? Обмазывать канцеляритом в 6 слоев, разумеется.


  1. Cheater
    30.09.2022 22:55
    +38

    О, студент-автор статьи прикольно врёт:

    let xs = vec!["lorem", "ipsum", "dolor"];

    xs.iter().filter(|item| item.ends_with('m')).nth(0)

    Да, исходный код стал значительно меньше, вот только получаемый машинный код на порядки вырос, да ещё и стал существенно медленней. Предлагаемая тут сущность, передаваемая в filter, и вовсе является замыканием из мира функционального программирования и представляется в машине весьма нетривиально; желающим предлагается посмотреть получаемый машинный код.

    Одна маленькая проблема, он забыл включить флаг оптимизации. Вот дизассемблированный код c "-C opt-level=2". "Желающим предлагается посмотреть" на 2 с половиной ассемблерные инструкции, в которые превратился вызов filter, а также почитать что такое zero cost abstractions в Rust...


    1. V1tol
      01.10.2022 00:26
      +22

      Самый залайканный первый комментарий на реддите раста к постам типа "код на расте медленнее, чем код на языке X" - "а вы в релиз режиме код собирали?"


    1. vkni
      01.10.2022 22:14

      Это синтетика, всё-таки — там всего три элемента, с которыми llvm делает истинные чудеса. Я даже не сильно удивлюсь, если в очередной редакции он подставит "lorem" вместо этого кода, сделав полное вычисление.

      Но, конечно, странно писать фильтр и брать первый элемент в энергичном языке. То есть, код

      head $ filter ( ((==) 'm') . last ) ["lorem", "ipsum", "dolor"]

      выглядит разумно. Но уже в Ocaml я бы использовал List.find:

      List.find (String.ends_with suffix:"m") ["lorem"; "ipsum"; "dolor"]

      Иначе для длинных списков работа далека от оптимальной.


      1. mayorovp
        01.10.2022 22:22
        +1

        Ничего странного: язык-то, может быть, и энергичный — а вот итераторы "ленивые".


        1. vkni
          01.10.2022 22:48

          Обычно за ленивость приходится платить... Не в этом случае, но.


      1. ZyXI
        02.10.2022 00:13
        +1

        Меня удивило, что при включении оптимизаций он не сократил весь main до одного ret — тут ни о каком lorem речи не идёт, main же ничего не возвращает, и не имеет никаких побочных эффектов, кроме выделения памяти. Именно так компилятор делает, если заменить vec! на &. Но почему‐то не если убрать vec! полностью (т.е. заменить тип xs с Vec<&'static str> на [&'static str; 3], или на &[&'static str; 3] в первом случае).


        1. AnthonyMikh
          02.10.2022 00:33

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


        1. Cheater
          02.10.2022 02:36
          +2

          Думаю дело в том, что когда создаётся локальная переменная типа [] или Vec, а не &[], компилятор должен создать и инициализировать её на стеке (в куче, для Vec), и догадаться что создавать её не надо он может только если к ней применяются константные методы (коим ends_with не является).

          Я заменил метод на константный и у меня вариант с Vec скомпилировался в один ret:

          (Дисклеймер: Максимально уродский хак, требующий unsafe nightly rust)

          #![feature(const_slice_index)]
          pub const fn ends_with_m(s: &str) -> bool {
              unsafe {
                  return *s.as_bytes().get_unchecked(s.len() - 1) == 'm' as u8;
              }
          }
          
          pub fn main() {
              let xs = vec!["lorem", "ipsum", "dolor"];
              xs.iter().filter(|s| ends_with_m(s)).nth(0);
          }
          


      1. 0xd34df00d
        02.10.2022 03:02

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


        1. vkni
          02.10.2022 07:09
          +1

          "Оптимизации хрупки, мой друг!"

          Реально, я видел подобное же на C++ с векторами и самописным zip'ом с лямбдами. Код совершенно неидеоматический, но компилятор "урезал осетра" до неотличимости от идеоматического кода. Но вот шаг влево, шаг вправо привёл к тому, что компилятор оптимизировать не смог, и получились несколько страниц ассемблера.


      1. Cheater
        02.10.2022 03:35
        +3

        Но, конечно, странно писать фильтр и брать первый элемент в энергичном языке.

        Просто кривой код. То, что нужно автору, называется find:

        let xs = vec!["lorem", "ipsum", "dolor"];
        xs.iter().find(|item| item.ends_with('m'));
        


    1. morijndael
      02.10.2022 00:52

      Емнип, это принципиальная позиция у них там, не врубать оптимизации, <sarcasm>ведь настоящий программист и без них может написать все идеально оптимально, а сложный компилятор такому программисту только мешает</sarcasm>


  1. rtemchenko
    30.09.2022 23:23
    +6

    Падажжите. Где опровержение изначальной цитаты в статье?

    Я мимокрокодил. Но вот это "как заставить тупой компайлер сделать то, чего я хочу" сплошь и рядом при работе с "продвинутыми" компиляторами.

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

    Это я конечно утрирую. Но я был бы осторожен с теми, кто говорит "Долой С, перепишем все на Раст и заживем". Впрочем тех кто хочет все переписать на С я бы тоже обходил стороной. :)


    1. mayorovp
      30.09.2022 23:46
      +6

      Её и опровергать нечего, это же очевидная неправда.


      Дело скорее в том, что компилятор раста сам решает, когда владение "должно" (с его, компилятора, точки зрения) перейти от одного игрока к другому. А решать это вообще-то должен программист, а не компилятор.

      При передаче по значению происходит передача владения, если передать ссылку или клонировать объект — передачи владения не происходит. Писать или не писать & там, .clone() или вовсе поставить трейт Copy решает не компилятор, а программист. Всё.


      1. nuclight
        01.10.2022 02:22

        И эти вещи сто лет как называются "make compiler happy" в комментариях в коде, даже на Си. То есть если посмотреть с более высокого уровня, цитата таки права — программист вынужден подстраиваться под компилятор.


        1. lorc
          01.10.2022 08:04
          +9

          И эти вещи сто лет как называются "make compiler happy" в комментариях в коде, даже на Си.

          В 99% таких случаев компилятор всячески пытался предостеречь программиста от ошибки. Просто программист этого не понимал.


        1. Halt
          01.10.2022 08:13
          +17

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

          Эти вещи определены однозначно, как раз таки в отличие от компилятора C++, который, например, может делать copy elision или «продление времени жизни», а может и нет, по желанию левой пятки.

          Вот как раз в C++, продление времени жизни является адским хаком, не вытекающим напрямую из семантики, про который, по видимости говорят Столяров и К°, транзитивно перенося его на Раст.


  1. thevlad
    30.09.2022 23:31
    +27

    Уровень исходной(rustmustdie) статьи пробивает пол, хотя я и не фанат раста. Обычно то, что обозначается в статье, как zero runtime, называется bare metal, потому что именно эмбедерам оно и нужно. И ищется как bare metal rust.

    PS: о кто-то обиделся и решил в карму плюнуть ) возможно было недопонимание, поправил, речь шла не о статье на хабре


    1. Deosis
      03.10.2022 09:29
      +4

      Я бы даже сказал, что rustmustdie - это очень жирный наброс.

      К примеру сборщик мусора std::rc::Rc;

      Тогда в C++ есть как минимум 3 сборщика мусора (unique_ptr, shared_ptr, auto_ptr)


  1. codecity
    30.09.2022 23:33

    А кто смотрел тонкости лицензирования Rust этого? Не может с ним произойти то же что с Java?


    1. DirectoriX
      01.10.2022 05:13
      +3

      Компилятор и стандартная библиотека очень даже свободные:

      Rust is primarily distributed under the terms of both the MIT license and the Apache License (Version 2.0), with portions covered by various BSD-like licenses.


    1. mrsantak
      01.10.2022 09:40

      А что такого произошло с Java?


      1. vassabi
        01.10.2022 13:11

        у него были Sun и Oracle


        1. mrsantak
          01.10.2022 17:45
          +1

          Были, а произошло-то что?


          1. vassabi
            01.10.2022 22:20

            было много разного в его истории, чего хотелось бы избежать :)

            (ну, если вы не корпорация с толстым кошельком)


            1. siziyman
              02.10.2022 17:38
              +2

              Это всё, конечно, не самые приятные вещи, которые вообще никак не ограничивают 99,99999% разработчиков на Java. То есть проблемы с прагматической точки зрения пользователя языка не существует, проблема существует разве что этическая.


              1. vassabi
                03.10.2022 00:02
                -2

                если вы школьник и пишете лабораторные работы - то без проблем. Если вы неуловимый джо или пишете бесплатный опенсурс - то тоже без проблем.


                1. siziyman
                  03.10.2022 01:27
                  +2

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


  1. Janycz
    01.10.2022 00:23
    -1

    После прочтения возник такой вопрос: можно ли на Rust писать как на C? Т. е. используя по максимому стандартную библиотеку языка С, без напряга с borrow checker, не парясь над концепциями владения, оборачивая почти все в unsafe и т. д. и т. п.


    1. thevlad
      01.10.2022 00:43
      +7

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


    1. 0xd34df00d
      01.10.2022 00:45
      +7

      Можно, но зачем?


      1. Janycz
        01.10.2022 00:55
        -6

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

        P. S. На C# я когда-то занимался программированием как на С, просто нравилось так писать.


        1. mayorovp
          01.10.2022 01:17
          +9

          надо реализовать некоторую функцию на Rust (и желательно побыстрее, еще вчера), а стандартную библиотеку или плохо знаю, или не знаю

          Звучит как то, чего нельзя делать ни в коем случае.


          А так — сырые указатели есть, что ещё надо сишнику? :-)


          1. Janycz
            01.10.2022 01:25

            Не, ну то, что сырые указатели есть, это уже хорошо. А как в Rust со всякими кастами? Как borrow checker на указателях работает?


            1. snuk182
              01.10.2022 07:12
              +2

              Работает, как и с любыми другими типами, с поправкой на статический / динамический полиморфизи (типажи в помощь). Касты есть, но они для нубов / отчаянных / бетмена.


      1. kmeaw
        01.10.2022 16:28

        Например для того, чтобы оттранслировать существующий C-проект, а потом, постепенно, переписывать его части, используя возможности Rust. Как режим BetterC в D.


  1. crackidocky
    01.10.2022 00:29
    +10

    Студент в упомянутой статье еще вроде бы взъелся на синтаксис в Rust. До меня немного не доходит почему. Я в разработке сравнительно немного (3 года) и системным программированием не занимался, но даже для того кто писал три года на TypeScript - синтаксис в Rust достаточно интуитивный.
    Более того, чем больше пишешь на Rust, тем больше понимаешь, что синтаксис очень похож на TypeScript.

    • Структуры - интерфейсы с конструктором

    • Имплементации - все те же интерфейсы

    • Трейты - абстрактные интерфейсы

    В синтаксисе все достаточно легко, даже те же стрелочные функции сделаны достаточно просто

    fn main() {
        let n = 2;
        let x = 3;
    
        let string_concat = |x: i32, n: i32| -> String {
            x.to_string() + &n.to_string()
        };
    
        println!("{}", string_concat(x, n));
    }
    

    Да и сам студент почему-то вечно говорит про синхронный код, кой его в обычных задачах иногда бывает меньше, нежели асинхронного (HTTP-запросы, сокеты, файлы и так далее)

    Также сборщик мусора о котором говорит учащийся - далеко не тот сборщик мусора, что например в JS или Python.

    Автору респект за тщательный разбор материала

    Из прочего:

    • Идея того, что в Rust отказались от null - просто превосходная, без него намного легче.

    • switch (тут он match) наконец-то стал выглядеть красиво, а не как огромное количество case

    • Идея с заимствованием понятна с самого начала, сделано для того чтобы никто не изменил переменную без нашего ведома

    • Там еще учащийся упоминал о том, что непонятно зачем теперь уровни вложенности, если все переменные будут все равно дропаться в конце. Они нужны чтобы не было вот такого:

    fn main() {
        {
            let x = 32;
        }
    
        println!("{}", x); // Не скомпилируется
    }
    


    1. alexxisr
      01.10.2022 18:31
      +6

      фигасе интуитивный. вот мне как ни разу не писавшему на расте не понятно - зачем println! восклицательный знак? ну предположим, что просто так функцию назвали. Но почему x.to_string() без & а &n.to_string() с ним? и зачем в println! первый аргкмент "{}"? это же явно не формат как в С-ной printf


      1. mayorovp
        01.10.2022 18:38
        +1

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


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


        1. Alex_ME
          01.10.2022 22:07

          > Но почему нельзя было сделать перегрузку для двух строк — для меня тоже загадка

          1. Ну так если оператор будет принимать две владеющие строки, то он заберёт владение обоими. И если левый аргумент возращается как результат конкатенации, то правый аргумент был бы забран и дропнут.
          2. При сложении строк нем нужен буфер большего размера. И тут либо выделить новый, либо расширить буфер одной из них (т.е. мутировать одну из них). И текущая семантика это показывает.


          1. mayorovp
            01.10.2022 22:23

            Ну так если оператор будет принимать две владеющие строки, то он заберёт владение обоими. И если левый аргумент возращается как результат конкатенации, то правый аргумент был бы забран и дропнут.

            А что в этом страшного?


            При сложении строк нем нужен буфер большего размера. И тут либо выделить новый, либо расширить буфер одной из них (т.е. мутировать одну из них). И текущая семантика это показывает.

            Ну так и предлагаемая семантика это тоже позволяет.


            1. vassabi
              01.10.2022 23:55

              в Rust же можно расширить существующий оператор - написать сложение с двумя владеющими строками?

              почему бы не попробовать написать, и потом им попользоваться ?


              1. mayorovp
                02.10.2022 00:18

                Можно, но только в том же крейте в котором определены сами строки (т.е. в стандартной библиотеке).


                Т.е. если бы в стандартной библиотеке появился вот такой код:


                impl Add<String> for String {
                    type Output = String;
                
                    fn add(self, rhs: String) -> Self::Output {
                        self + &rhs
                    }
                }

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


                1. withkittens
                  03.10.2022 14:35

                  и новый бы чуть менее удивлял.

                  Вас бы действительно не удивляло, если бы оператор сложения дропал s2? :)

                  fn main() {
                      let s1 = "Hello ".to_string();
                      let s2 = "world!".to_string();
                      let s3 = s1 + s2;
                      println!("{}", s2); // oops: error[E0382]: borrow of moved value: `s2`
                  }


                  1. mayorovp
                    03.10.2022 14:47

                    Но ведь он прямо сейчас "дропает" s1. Что, s2 чем-то принципиально отличается?


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


                    1. withkittens
                      03.10.2022 15:26

                      Конечно. Буфер s1 по возможности будет переиспользован (если там хватает места для s2). Для этого нам нужно отдать владение s1 сложению, взамен мы возможно избегаем лишнего выделения памяти. s2 нужна только для чтения.

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

                      Синтаксически всё было бы верно, да. Идея в том, что если я вам даю дискету, чтобы вы с неё скопировали данные, я не хочу, чтобы вы её у меня забирали себе.


        1. AnthonyMikh
          01.10.2022 23:55
          +1

          Но почему нельзя было сделать перегрузку для двух строк — для меня тоже загадка.

          Потому что str является примитивным типом и потому методы на нём, включая реализацию трейта core::ops::Add, могут быть только в крейте core. Результатом подобного сложения должна быть выделяющая в куче память String, определение которой принадлежит крейту alloc, разумеется, зависящему от core. Написать реализацию такого сложения строк в core нельзя, потому что в этом случае core зависела бы от alloc, а циклические зависимости между крейтами недопустимы. Написать же реализацию сложения строк в alloc нельзя, потому что str — примитивный тип.


          В любом случае, в примере выше проще было бы вместо сложения написать format!("{x}{n}"). И, кстати, к вопросу о том, почему это макрос, а не функция: с функцией вы не смогли бы интерполировать переменные.


          1. mayorovp
            02.10.2022 00:20
            +1

            Речь шла о сложении двух alloc::string::String, оператор для которых без проблем можно было бы определить в alloc::string, прямо рядом с оператором сложения alloc::string::String и str.


            1. AnthonyMikh
              02.10.2022 00:34

              Каюсь, проглядел.


      1. Mingun
        01.10.2022 18:58
        -1

        Вот вообще странные претензии.


        зачем println! восклицательный знак?

        А почему в питоне через % от форматной строки нужно аргументы перечислять?


        и зачем в println! первый аргкмент "{}"? это же явно не формат как в С-ной printf

        А почему он должен им быть?


        1. mrsantak
          01.10.2022 21:52

          А почему он должен им быть?

          А зачем для вывода одной строки вообще какое-то форматирование? Почему нельзя просто передать string_concat(x, n) единственным аргументом в println!? Я хз как оно в расте, но в java подобное было бы индусским кода уровня if(value) return true; else return false;


          1. mayorovp
            01.10.2022 22:06
            +1

            По той же причине почему нельзя передать единственную строку в printf...


            Кстати, вот вариант без форматирования:


            stdout().write_all(string_concat(n, x).as_bytes()).unwrap();

            Проще не получилось...


  1. KivApple
    01.10.2022 04:24
    +1

    Скажу неожиданный минус Раста - отсутствие нормальной поддержки сишных строк. Подавляющее большинство нативных библиотек имеет Си совместимый интерфейс. А в расте для стандартных CStr/CString даже трейт Display не реализован. И как литерал их из коробки записать нельзя. Предлагается конвертировать строки туда сюда на каждый чих. Что мешало добавить в стандартную библиотеку несколько функций?


    1. snuk182
      01.10.2022 07:20
      +16

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


  1. edo1h
    01.10.2022 06:51

    Намного важнее сам принцип: язык Си позволяет полностью отказаться от возможностей стандартной библиотеки

    может быть и позволяет, только пример совершенно не соответствует заявленному «работающая (вызывающая сисколы) программа на си без байта внешних зависимостей».


  1. netch80
    01.10.2022 11:06
    -9

    > Но это бы лишний раз показало, что преподаватель, который влил эту чушь в голову студентам МГУ, не просто находится не на своем месте, он вообще ничего не смыслит в системном программировании.

    Когда оценивают подобное утверждение, надо понимать минимум две вещи:

    1. Чему равно «все» = «универсальное множество» в рассмотрении. Потому что всегда есть, например, где-нибудь какой-то наспех сляпанный одним студентом язык, который даёт 1/4 того, что есть в C89, при этом точно так же не требует стандартной библиотеки, но достаточен для написания драйверов. (Или, возьмём пример ещё грубее, язык, использованный марсианами. Мы про них ничего пока не знаем.) Естественно, он не рассматривается, и «все» будет включать «все достаточно известные, чтобы попасть в рассмотрение», и это не будет являться ошибкой автора.

    2. Когда именно было написано цитируемое утверждение. Книги Столярова впервые вышли задолго до появления Rust. Такие источники всегда немного отстают от реальности, и всё переписать невозможно.

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


    1. Quark-Fusion
      01.10.2022 17:19
      -1

      2. образование отстаёт от реальности — поправил
      если хотите актуальных знаний не надо туда вообще лезть

      я вот может не знаю, образование учит анализировать источники?


  1. netch80
    01.10.2022 11:11

    И ещё:

    > Настало время реализовать вывод «Hello, world!» в стандартный поток вывода! \<Не забыть изменить на менее глупую фразу перед публикацией>.

    1. Лучше использовать более привычные пометки типа XXX, TODO… — по ним легко искать.

    2. А что глупого-то в «Hello world»?

    Уж простите, что не про Rust.


    1. Browning
      01.10.2022 13:23
      +1

      Полагаю, это отсылочка к "убрать перед публикацией" из квеста, о котором недавно рассказывали на Хабре: https://habr.com/post/689116/


  1. inklesspen
    01.10.2022 11:27

    rustmustdie до сих пор кажется скорее шуточной статьей, нежели чем-то серьезным. В другой статье, cmustdie, где Алексей является соавтором, приводятся более адекватные (на мой взгляд) аргументы.

    Очень надеюсь, что через некоторое время автор скажет что-то на подобии "Расходимся, это была шутка".


  1. Apoheliy
    01.10.2022 13:12

    Прочитал. Оценил важность делать syscall "без нифига" (это действительно нужно? Для C? И для условного JavaScript-а тоже (как он только ещё живёт-то без этого)?).

    Далее ! САРКАЗМ !:

    Впечатление как об каком-то обществе защиты жуков-выберите-любой:

    • А-а-а, общество куропаток назвали нашего жука хуже бабочек-однолеток! Как они посмели!

    • Однако наш жук лучше: посмотрите, какие у него шейные мышцы!

    • Конечно же, в интернете кто-то неправ. Но почему все наезжают на нашего жука! Вот прям все-все-все!

    Это сильно напоминает стенания: про наших жуков забы-ы-ы-ы-ли!!! Как вы могли-и-и???

    конец сарказма.

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

    Возможно, будущие статьи писать без противопоставления с другими языками. В стиле:

    Посмотрите, какие прикольные штуки умеет Rust? Он может работать и без стандартной либы!


  1. vassabi
    01.10.2022 13:15
    +1

    Я бы хотел, чтобы в МГУ (самом МГУ!) ученые и студенты были открыты к познанию. Ведь в этом и есть суть университетов, нет? Слишком многого хочу?..

    хмм .. а представьте что завтра к вам университет придет и скажет "мы хотим, чтобы вы открыли нам глаза и пришли к нам на кафедру. Будете у нас вести курс, вместо вот этого косного и закрытого к познанию нехорошего человека" - они многого хотят или норм ? :)


    1. sshikov
      01.10.2022 16:05
      +5

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


  1. kuraga333
    01.10.2022 14:31

    А я вот не очень понял, почему rustc_codegen_gcc генерирует нечто другое, чем до его использования? В чём именно различия будут? Спасибо.


  1. steanlab
    01.10.2022 15:16
    +3

    Автору заслуженный плюс. И за статью, и за мотивацию к ее написанию
    p.s. сам ни разу не кодер, ни разу не знаком с rust, но прочел на одном дыхании вместе со всеми спойлерами :)


  1. jok40
    01.10.2022 18:02
    +8

    Считается, что 93.9% программистам именно такое поведение (автоматическое включение std и прелюдии) и требуется.
    Тем не менее, для остальных 19% программистов предусмотрен режим отключения стандартной библиотеки с помощью атрибута #![no_std]
    Вы же не думаете, что я тут беру статистику с потолка?
    Эээ… Нет, конечно, не думаем ни в коем случае! :)


    1. rafuck
      01.10.2022 20:45
      +6

      Ну это же сарказм. И первое, и второе, и третье.


  1. valeriyk
    01.10.2022 20:36
    +2

    Кстати, раз уж прописали panic = "abort", то extern "C" fn eh_personality() {} не нужна.


  1. Cykooz
    01.10.2022 22:26

    Finished dev [unoptimized + debuginfo] target

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


  1. grt_pretender
    02.10.2022 12:40

    «МГУ сделал замеры -- Rust должен умереть» было бы еще драматичней)


  1. gregorybednov
    02.10.2022 13:02
    +4

    Было занимательно прочитать!

    Кстати, если рассматривать не только Rust, то окажется, что на поверку утверждение А.В.Столярова ещё более ограниченное в плане рассмотрения.

    Ведь помимо традиционных высокоуровневых языков программирования, "уникального" C и языков ассемблера есть ещё Forth, чей "рантайм" для любой конкретной архитектуры железа программируется в том числе на голое железо даже на языках ассемблера без особых проблем с нуля за несколько дней работы (уточнение: при должном знании целевой архитектуры), после чего можно наращивать кодовую базу программируя в сравнительно высокоуровневом ключе. Более интересная эквилибристика - это написать форт-машину под множество архитектур, тут наиболее успешным стал проект CollapseOS. Еще из примечательного, этот язык программирования используется в загрузчике FreeBSD.

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


    1. WASD1
      02.10.2022 13:37
      +4

      Коллеги кто-нибудь может мне объяснить "за феномен Forth".
      Часто вижу вот такие вот восхваления про форт и блоги отдельных исследователей форсящих его.

      Но язык 1971 года не ставший популярным (да ещё и построенный на стековой машине - моде 1970х годов) меня очень смущает.
      Кто-то из инженеров может пояснить: есть что-то реально стоящее в языке (ну условно Haskell можно изучить для полезного расширения кругозора, Rust - для полезного изучения кругозора и для программирования) для его изучения или просто у языка есть группа преданных фанатов?


      1. gregorybednov
        02.10.2022 16:57
        +3

        Надеюсь, что Вам ответят, так как ситуация парадоксальная - упрощая, все люди делятся на тех, кто Форт не знает; тех, кто от него фанатеет и наконец тех, кто его ненавидит.

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

        1. Это язык программирования, машина исполнения которого понятна. В мире, где для одной из самых популярных компьютерных архитектур даже язык ассемблера в каком-то смысле "компилируемый", а не транслируемый (здравствуй, микрокод x86), где даже виртуальные машины интерпретируемых и JIT-компилируемых языков сложны, по-моему это приятная "отдушина".

        2. На более высоком уровне (как самого языка, так и владения им) Форт предлагает метапрограммирование, которое резко отличается от ООП в стиле C++ (и всех его потомков)

        Таким образом, язык похож на Лисп, но с некоторыми фичами (низкоуровневость, миниатюрность и исполнение на голом железе - очень удобно для микроконтроллеров; ещё есть интересные свойства из-за конкатенативности, так по идее можно например упрощать выражения). Также эта простота позволяет использовать его как основу примитивных DSL-процессоров, которые могут, например, обладать повышенной надёжностью (чем проще система, тем надёжнее) и использоваться, скажем, в космосе, или работать в наноботах, и т.п. нетривиальные задачи. Также идейный потомок Форта реализован в контроллере практически любого принтера (PostScript, привет!) и вообще это очень неплохо для встраиваемой техники.

        Есть и, казалось бы, очевидные недостатки: чаще всего программы на Форте падают от одной-единственной ошибки, многие программы с нуля пишутся как головоломка. Но тут важно понимать, что это не объективная проблема, а несовместимость с современным ИТ и методологией разработки - но и сейчас бывают и такие программы, в которых не должно быть ошибок, а вместо "программ с нуля" (каждая из которых в 80ые-90ые, как правило, была от одного вендора) иногда правда лучше работать с именованными функциями (словами).

        Мне очень понравилось определение Форта как "структурированного ассемблера": языка ассемблера некоторой стековой машины (возможно виртуальной, возможно физической), позволяющего без обыкновенных (для ассемблеров) проблем писать структурированный код.

        Но повторю, я скорее из "преданных фанатов" :D , но старался расписать как можно объективнее.


  1. Tell997
    02.10.2022 14:01
    +1

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


  1. erlyvideo
    02.10.2022 17:55
    +4

    Я в этом самом МГУ учился и Столярова видел.

    Мне очень интересно, когда и в какой момент ВМК МГУ стал авторитетом в области программирования.

    Когда я учился там почти 20 лет назад, я наблюдал давление на тех, кто пытался принести что-то новое. Учитывая, что имена не поменялись, непонятно с чего это давление должно было исчезнуть.

    Буду рад увидеть какие-то значимые достижения МГУ (и в частности ВМК) в практической области программирования. Аутсорсное батрачение на западные компании (что преподносилось как очень большое достижение) просьба не показывать, не релевантно.


    1. Cerberuser
      02.10.2022 20:43
      +2

      Не могу не попытаться призвать в тред товарища @3Dvideo- памятуя о прослушанном (к своему стыду, наполовину) курсе его авторства, думаю, их кругу будет что ответить.


  1. JustForFun88
    02.10.2022 23:13
    +4

    Что мне иногда раздражает, так это то что многие говорят под капотом rust использует много unsafe, и что нельзя превратить unsafe в safe.

    Хотя вот пример как из превратить unsafe в safe и получать PROFIT :-). Привожу адаптированный код хеш таблицы из библиотеки hashbrown:

    pub struct RawTable<T, A: Allocator + Clone = Global> {
        table: RawTableInner<A>,
        // Tell dropck that we own instances of T.
        marker: PhantomData<T>,
    }
    
    pub struct RawTableInner<A> {
        bucket_mask: usize,
        ctrl: *const u8,
        growth_left: usize,
        items: usize,
        alloc: A,
    }
    
    pub fn get<F>(&self, hash: u64, eq: F1) -> Option<&T>
    where
        F: FnMut(&T) -> bool,
    {
        match self.find(hash, eq) {
            // bucket = *mut T
            Some(bucket) => Some(unsafe { &*bucket }),
            None => None,
        }
    }
    

    Прикол в том что вроде бы как RawTableInner хранит сырой указатель на аллоцированную память и полностью небезопасен. Но далее привязываем данную таблицы к RawTable + PhantomData что говорит компилятору что у нас владеющая структура. Потом определяем метод get, который внутри использует метод find. Find возвращает сырой указатель. Но!!! Мы ведь определили метод get у &RawTable, а также привязали выходную ссылку на значение к времени жизни самой RawTable, то есть выходная ссылка будет всегда валидна, так как нельзя вернуть ссылку на удаленную таблицу.

    То есть, метод find который возвращает полностью небезопасный "сырой указатель" который может существовать даже после удаления RawTable, и по определению unsafe, превратился полностью в safe. Так как с помощью метода get мы явно привязали жизнь сырого указателя к жизни RawTable. Кроме того мы привязали его не просто к таблице. Мы еще и заблокировали изменения самой RawTable, пока существует на него ссылка. И все это в 10 строк кода (функции get).

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


  1. mikhanoid
    02.10.2022 23:19
    +2

    Именно это свойство — zero runtime — делает Си единственным и безальтернативным кандидатом на роль языка для реализации ядер операционных систем и прошивок для микроконтроллеров. Тем удивительнее, насколько мало людей в мире этот момент осознают; и стократ удивительнее то, что людей, понимающих это, судя по всему, вообще нет среди членов комитетов по стандартизации (языка Си)…

    — А. В. Столяров

    Нет взаимосвязи между наличием у языка runtime и возможностью реализации на этом языке ядер операционных систем и прошивок для микроконтроллеров. Я знаю о проектах на диалектах Lisp, но, наверняка, есть проекты и на других языках. На Lisp написаны: Mezzano, Loko Scheme, Maker Lisp, LisPi, PilOS, PICOBIT и т.д.

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


    1. AnthonyMikh
      03.10.2022 16:19

      на основании малозначимых свойств языка

      Возможность просто писать программы так, чтобы они не разваливались от малейшего дуновения — это малозначимое свойство?