All streams
Search
Write a publication
Pull to refresh
36
0
Send message

язык не должен исполнять роль нянечки в детсаду

Практика (которая критерий истины) показывает, что писать безопасно на C и/или C++ не может никто. Rust позволяет людям с меньшим опытом писать настолько же быстрые и гораздо более безопасный (уменьшение количества уязвимостей в 70 раз это слишком хорошо, чтобы от этого отказываться).

любой объект в системе должен иметь "операционный дескриптор"

Только систем таких нет. В Rust как раз по умолчанию все это и проверяется (как на этапе компиляции, так и в рантайме).

  1. Не согласен, что "mission critical" обязан быть высокоэффективным. Код установок для рентгеновского излучения очень даже "mission critical", но 25млн. человек ему одновременно обслуживать не надо.

  2. Эта статья и так огромная, я половину статьи выкинул и еще половину от оставшегося засунул под разные спойлеры, чтобы их можно было пропустить если не интересно. Если я начну говорить о всем, что в Rust хорошо, то эта статья бы: 1) была бы раз в 5 больше; 2) вряд ли когда-либо вышла, мне и эти мысли оформить в статью было непросто. Про скорость работы Rust есть куча других статей, если вкратце, то он +- аналогичен по скорости с C. У меня же была другая задача, я хотел показать, что как раз кроме скорости в Rust есть очень много всего.

  3. Как раз таки наоборот, после знакомства с Rust и более полного понимания "как же оно внутри устроено" я "боюсь" C и/или C++ еще больше. Огромное количество способов потратить недели своей жизни на поиск очередного UB в абсолютно обычном коде бизнес-логики (а не в коде супер-оптимизированных алгоритмов) для меня просто неприемлемо.

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

  5. Статей про прямое сравнение по разным параметрам тоже огромное количество, можете без особых проблем их найти. Мне не хотелось писать "тоже самое, чуть другое".

Не понял ваш комментарий, Rust компилятор уже есть и я про него и писал. Никаких отдельных компиляторов не нужно.

Специально для вас у меня есть ссылка (там первые минут 10 про это), которая все это недопонимание объяснит! Тот факт, что в языке и библиотеках на порядки меньше уязвимостей, чем в C и/или C++ и то, что сообщество очень пристально следит за тем, чтобы в публичных библиотеках не использовался unsafe код направо и налево дает очень хорошую гарантию. Так то никакой язык не безопасный, Python внутри на C, но при этом UB там получить крайне маловероятно.

Часть про "Как бы я не относился к C, все же ему больше 50 уже, не те времена были" вы, видимо, упустили. Странно было бы предъявлять к языку 1972 года претензии на счет нововведений 21 века, я этого, мне кажется, и не делаю (хотя слышал, что даже в свое время в этом плане C не блистал и Fortran и Pascal во многом уже тогда были лучше).

Про сложность ревью кода в Beyond Safety and Speed: How Rust Fuels Team Productivity говорил Lars Bergstrom. По внутренним опросам в Google android, 50% опрошенных сказали, что код на Rust проще проверять (возможно, что это по сравнению с C++ и тогда это не то, чтобы сильно впечатляюще). Так что это дело просто привычки, вместо поиска скрытого UB можно сконцентрироваться на других вещах.

Я бы даже сказал, что лучше не лениться и для такого использовать паттерн New Type. Это позволяет гарантировать, что невозможно вместо старого типа использовать новый. Да, иногда приходится реализовывать базовые операции над типами заново, но гарантии того стоят.

У меня в моем трассировщике как раз есть такой пример: в оригинальной статье на C++ использовались как раз алиасы для color, vec3 и point3. Я не поддаваться на эту легкость и сделал три независимых типа (заодно узнал, как писать свой derive чтобы не копипастить базовые операции над типами).

Особенно если где какой тип поменять надо

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

У всех свои приоритеты, мне после Rust и про Python сильно долго думать не хочется, в то тоска одолевает. Я понял, что Golang мне абсолютно не нравится когда проходил A Tour of Go и за сколько там упражнений нашел 4 возможности отстрелить себе лишнюю конечность:

  • возможность читать слайс после длины;

  • nil map, которая не позволяет делать с собой ничего полезного (хотя nil слайс на практике идентичен слайсу нулевой длины);

  • поведение nil и закрытых каналов;

  • возможность переопределять ключевые слова (знаменитое #define true (rand() > 10) //Happy debugging suckers).

Гонка данных и состояние гонки это две разные проблемы, они даже подклассами друг друга не являются. Цитата из The Rustonomicon:

Safe Rust guarantees an absence of data races, which are defined as:
- two or more threads concurrently accessing a location of memory
- one or more of them is a write
- one or more of them is unsynchronized
A data race has Undefined Behavior, and is therefore impossible to perform in Safe Rust. Data races are mostly prevented through Rust's ownership system: it's impossible to alias a mutable reference, so it's impossible to perform a data race. Interior mutability makes this more complicated, which is largely why we have the Send and Sync traits.
However Rust does not prevent general race conditions.

Насколько я знаю, есть одну неожиданную проблему, которую это может вызвать - если все же библиотеки A и B обмениваются типами из C, то можно получить ошибку expected struct C::InnerType, found a different struct C::InnerType; note: perhaps two different versions of crate C are being used? В остальном, насколько я знаю, проблем это не вызывает.

И кстати, если вас хоть немного заинтересовал Rust, то еще раз рекомендую лекции Алексея Кладова. Там всего 13 лекций по полтора часа, но этого с головой хватит, чтобы понять что, как, зачем и почему в Rust. Особенно по сравнению с моими мыслями, которые я кое-как собрал в статью.

Кстати, раз уж про поддержку зависимостей заговорили. В Rust есть потрясающая возможность иметь одну библиотеку нескольких разных версий без конфликтов (если не надо между разными версиями взаимодействовать). Так что если ваша зависимость A имеет подзависимость C версии 1.0.0, а в зависимости B подзависимость C версии 2.0.0, то они спокойно продолжат работать. Можно даже импортировать свою собственную библиотеку другой версии (это используется, например, чтобы пофиксить баг только в самой новой версии, а в старых просто импортировать из новой и не поддерживать несколько версий фиксов).

Почитал статью и так и не понял чем раст лучше, ..., но что мешает использовать пул потоков в с++

Видимо у меня не получилось расставить акценты в нужных местах. Преимущество Rust не в том, что есть библиотека, в которой пул потоков есть (и даже не в идиоматичном варианте, когда для того, чтобы сделать код многопоточным надо добавить .par_bridge().into_par_iter() и все), а в том, что компилятор гарантирует отсутствие гонок данных. Этот пример важен не самим кодом, а текстом после, т.е. пока я писал этот пример я сделал много разных ошибок, которые бы не помешали скомпилироваться C и/или C++ коду, но помешали скомпилироваться коду на Rust. Сила Rust в гарантиях компилятора, который позволяет экспериментировать. В safe невозможно скомпилировать программу там, чтобы передать одну задачу в 2 разных потока и потом дебажить гонки данных (можно послать копию задания, но это будет просто бесполезной работой, а не UB). Я могу позволить себе попробовать написать код без мьютексов, т.к.если я сделаю в нем ошибку, код просто не скомпилируется. В большинстве других языков конкурентность связана с огромным количеством граблей, которые приходится ловко обходить.

curl

Curl не то, что бы прям переписывают на Rust, но как минимум некоторые новые части пишутся на Rust. Если интересно, можете почитать про недавнюю уязвимость в curl, там в том числе и про переписывание на Rust.

SQLite

Не уверен, насколько они production ready, но на Rust пишут и базы данных, и key-value хранилища и прочие вещи, но сам язык в 3 раза моложе SQLite, так что трудно винить в этом язык.

Все же, "переписать все на Rust" это не то, что бы реальная цель или задача. Код, который полировали десятилетия никто просто так выкидывать не будет (уж точно не в первую очередь). Я поэтому и написал, что для меня у C и/или C++ ниша одна - легаси код, который слишком долго или дорого переписать.

в составе vcpkg около 2,5 тысяч библиотек

В Cargo.io на момент записи 141,981 пакетов. Это не честное сравнение, Rust поощряет разбиение пакетов на много мелких для переиспользования и ускорения компиляции, так что это не 141к обособленных пакетов, но все же.

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

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

Вот в чем точно "недостаток" (сами решайте, насколько это действительно недостаток) так это в том, что он заставляет очень критически относится к другим языкам.

После Rust я просто не смог смотреть на Golang, там слишком много вещей, которые должны были бы быть лучше. На C++ я тем более не посмотрю (о чем и в статье написал), для меня у него нет ни одного преимущества перед Rust и огромный товарный состав недостатков.

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

Всё УБ от оптимизаций на предположениях

Не готов ни подтвердить, ни опровергнуть, моих компетенций тут недостаточно. Но писать на C или C++ чтобы потом код без оптимизаций компилировать вряд ли кто-то будет. Проще сразу взять более медленный, но безопасный язык.

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

P.s. если кто-то хочет мне платить за рекламу Rust (хотя бы шоколадными медальками) то пишите в личку!

Но это справедливо для небольших проектов.

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

К сожалению, у меня нет личного опыта подобных проектов в Rust. Мое мнение о Rust основано на опыте больших проектов в других языков, но о больших проектах говорят в Fast Development In Rust, Part One, Beyond Safety and Speed: How Rust Fuels Team Productivity, Grading on a Curve: How Rust can Facilitate New Contributors while Decreasing Vulnerabilities и нескольких реддит постах из начала статьи, так что мне кажется, что моя экстраполяция вполне валидна.

Лично я для большого проекта брал бы язык с более очевидным синтаксисом

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

Да, статью "что не так с Golang" я бы почитал.

Это скорее шутка, у меня, все же, слишком мало опыта в Golang, буквально 2 недели попытки его выучить и пара месяцев обсуждений с друзьями "как так оказалось, что в Golang все настолько плохо". Просто когда в официальном курсе на официальном сайте Golang я умудрился задуматься "хм, кажется это какой-то бред" (и при этом я оказывался прав) раза 4.

Но уже много кто написал такие статьи, я рекомендую I want off Mr. Golang's Wild Ride, Lies we tell ourselves to keep using Golang, They're called Slices because they have Sharp Edges: Even More Go Pitfalls, Golang is not a good language.

Для примера добавлю сюда примеры кода, который вызывает у меня вопросы в духе "а хоть кто-то при разработке над чем-то кроме GC и async рантайма думал вообще?":

Заголовок спойлера
package main

import "fmt"

func main() {
    s := []int{2, 3, 5, 7, 11, 13}
    printSlice(s)

    // Slice the slice to give it zero length.
    s= s[:0]
    printSlice(s)

    // Extend its length.
    s = s[:6]
    printSlice(s)
    
    s= append(s, 7)
    printSlice(s)
    
    // Works just fine
    d := s[0:cap(s)][11]
    fmt.Printf("len=%d cap=%d d=%v\n", len(s), cap(s), d)
    
    // Obviously this panics
    g := s[11]
    fmt.Println(g)
}

func printSlice(s []int) {
    fmt.Printf("len=%d cap=%d %v\n", len(s), cap(s), s[0:cap(s)])
}
package main

import (
	"fmt"
	"math/rand/v2"
)

type SlidingWindow struct {
	s      []int
	offset int
	size   int
}

func (w *SlidingWindow) next() []int {
	if w.offset > (len(w.s) - w.size + 1) {
		return nil
	}
	t := w.s[w.offset : w.offset+w.size]
	w.offset += 1
	return t
}

func sliding_window(s []int, offset int, size int) []int {
	return s[offset : offset+size]
}

func main() {
	s2 := []int{1, 2, 3, 4, 5, 6}
	if rand.IntN(10) > 5 {
		s2 = append(s2, 7) // panics without append
	}

	sw := SlidingWindow{s2, 0, 3}

	for w := sw.next(); w != nil; w = sw.next() {
		fmt.Println(w)
	}
}
package main

import (
	"fmt"
	"math/rand/v2"
)

func really_big_func() (a, b bool) {
	// a lot of code
	// Happy debugging, suckers 
	true := rand.IntN(10) > 5
	false := rand.IntN(10) > 3
	fmt.Println(true)
	fmt.Println(false)
	// a lot of code
	return true, false
}

func main() {
	fmt.Println(really_big_func())
}
package main

type Container struct {
	a     int              // old field
	Items map[string]int32 // new field
}

func (c *Container) Insert(key string, value int32) {
	c.Items[key] = value
}

func main() {
	c := Container{a: 2}
	c.Insert("number", 32)
}

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

Information

Rating
Does not participate
Registered
Activity