Pull to refresh
45
0

Типострадалец

Send message

Представьте, что у вас enum имеет один из элементов MyEnum::Variant10(u8, u8, u8), а все остальные MyEnum::Variant0-9(u8), память будет выделена так:

  1. дискриминант (по умолчанию usize т.е. 4 или 8 байт)

  2. 3 байта Итого: 11 байт на adm64

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

enum A {
    V0(u8, u8, u8),
    V1(u8, u8, u8),
    V2(u8, u8, u8),
    V3(u8, u8, u8),
    V4(u8, u8, u8),
    V5(u8, u8, u8),
    V6(u8, u8, u8),
    V7(u8, u8, u8),
    V8(u8, u8, u8),
    V9(u8, u8, u8),
    V10(u8),
}

fn main() {
    println!("{}", std::mem::size_of::<A>());
}

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

То есть этот unsafe опять даже ноду в трех двусвязных списках держать не позволит?

А, простите, зачем?

Что поливается говном, если никакой реализации профилей безопасности пока не существует?

Идея, что код на C++ можно корректно автоматически анализировать по функции за раз, не требуя при этом аннотаций от программиста. В исходном коде на C++ попросту недостаточно для этого информации.

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

Шон и его фанаты как раз таки поливают профили говном при каждой возможности.

И правильно делают, потому что профили в том виде, в котором их продвигают, не работают и принципиально работать не могут.

Вы про то, что надо знать детали реализации slice, чтобы им пользоваться? В остальном вроде всё терпимо.

Ох, если бы только это.

nil

append в nil-слайс возвращает слайс с единственным значением, но попытка вставки в nil-мапу паникует. Функцию cap можно вызвать на слайсе, но не на мапе.

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

Имена переменных могут затенять встроенные идентификаторы:

package main

import "fmt"

func main() {
	fmt.Println(true) // true
	true := 42
	fmt.Println(true) // 42
}

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

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

У интерфейсов тоже есть нулевое значение, nil, и это отличается от интерфейса, который хранит nil-значение:

package main

import "fmt"

type I interface {
	f()
}

type C struct{}

func (c *C) f() {}

func main() {
	var in I
	fmt.Println(in == nil) // true
	var c *C = nil
	in = c
	fmt.Println(in == nil) // false
}

Переменные цикла переиспользуются между итерациями, что в своё время привело к отзыву трёх миллионов сертификатов Let's Encrypt. Сейчас это поправили, но более разумное поведение, ЕМНИП, всё ещё opt-in.

Каналы могут быть в двух невалидных состояниях: nil и закрытом. Посылка и получение в nil-канал блокируются навечно, в то время как отправка в закрытый канал паникует, а получение из закрытого канала возвращает нулевое значение. Зачем разница в поведении - неясно.

defer вызывается в конце функции. Функции, а не лексической области видимости.

Одно ключевое слово for имеет 11 разных значений в зависимости от семантики. У четырёх из них поведение зависит от того, по какому типу итерация, но синтаксис при этом идентичный. А, и ещё можно итерироваться с одной переменной по слайсу и получить индексы вместо значений.

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

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

По моему, как-то многовато надо в голове держать для так называемого простого языка.

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

Только [[maybe_unused]] ещё докинуть, чтобы компилятор на определение гуарда не ругался.

А этот антипаттерн от языка не зависит

В общем, property-based testing — это эксперимент, который надо уметь ставить, и его результаты тоже надо уметь обрабатывать. Об этом прекрасно рассказал автор QuickCheck в видео John Hughes — Building on developers' intuitions (...) | Lambda Days 19.

Понимаю, что статья давняя, но можете вставить ссылку на искомое видео, чтобы читателям не пришлось искать самостоятельно?

Значит, вы ни разу в жизни не пробовали писать эффективные

  • стейтмашины

  • парсеры

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

  • зависимое выделение/освобождение ресурсов

А вот использование goto для освобождения ресурсов - это так костыль в сишечке. В практически всех языках для этого есть более вменяемые инструменты: RAII в C++/D/Rust, try-with-resources в Java, using в C#, context managers в Python, bracket pattern в StandardML/Ocaml/Haskell. Причём конкретно RAII напишет ровно такой же код, что и сишник руками, только без ошибок.

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

Разбиение по парам зачем-то написано в виде tt-muncher, и это при том, что его можно написать напрямую:

macro_rules! pairs {
    ($($a:tt $b:tt)*) => { with_firsts($($a),*); with_seconds($($b),*); }
}

Зато если запустить санитайзер gcc, вот он вдоволь срабатывает.

Если вы именно санитайзер имели в виду, то тут как раз нет ничего удивительного: статические анализаторы и динамические детектируют разные баги, с довольно небольшим пересечением.

Для Rust было бы неплохо. Есть Clippy, но там, ЕМНИП, нет никаких кросс-функциональных вариантов анализа, только в пределах одной функции. Ну и некоторые ошибки, которые PVS-studio детектирует, от языка слабо зависят (вроде V6004)

хотя большие языковые модели уже на пятки наступают

Они никогда не заменят статические анализаторы, потому что:
а) никакая модель, какой бы навороченной она бы не была, не может диагностировать паттерны ошибок, которых не было в обучающих данных, и добавить новое правило в анализатор проще, чем дообучать модель на многих примерах.
б) LLM склонны к галлюцинациям, причём довольно убедительно выглядящим, а от качественного статанализатора ждут низкого false positive rate.

Ну да, опыт показывает, что они ещё и просто криво работают.

То, что MinGW пытается имитировать unix-like окружение на винде

язык молод и ограничен в возможностях

Тем временем возможности C

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

1
23 ...

Information

Rating
Does not participate
Registered
Activity