В случае с встраиваемым ассемблером и, возможно, атрибутом no_mangle, нельзя получить unsound код, что бы ты ни делал.
Можно, и очень легко. Ассемблер позволяет написать инструкцию, для которой поведение процессора буквально может быть не определено, а также написать код, который ломает правила языка. Через no_mangle же можно сделать свою функцию, которая пересечётся по имени с функцией из зависимости из нативного кода, перекроет оригинальное определение с абсолютно другим ABI и разломает всю программу, если дотуда дойдёт исполнение.
Тут не всё логично как пишет автор, потому что строка принадлежит функции мейн и совершенно непонятно почему вдруг процесснг забрал над ней владение если процессинг тоже принадлежит функции мейн и почему нужно её клонировать чтобы внутри мейна использовать два раза.
Потому что код так написан. Если нужно просто на печать выводить, то владение строкой можно и не принимать, а принимать лишь ссылку. Тогда можно будет и вызвать два раза подряд.
Про null это вообще бред, вот у нас есть некий класс который может быть нулл. В расте для такого случая предполагается сделать указатель на этот класс опциональным и проверять на None. Но если всё равно проверять на None в чём проблема блин проверить на null? Это буквально тот же самый if !class.is_none только if class != null. Изобрели костыль и ходят радостные.
Фишка в том, что в Rust можно не делать Option и тогда нигде на отсутствующее значение проверять не надо. В других языках, где null есть, обычно он является допустимым значением для всех ссылочных типов, а записать тип, который null в число допустимых значений не включает, нельзя.
Авторы языка не осилили написать проект, ради которого язык и создавался
Уточнение: проект начали писать задолго до релиза 1.0, когда в Rust ещё был сборщик мусора и зелёные потоки, которые можно было смешивать с нативными. И да, CSS-движок Stylo оттуда в итоге в Firefox перенесли.
Для пытливых умов есть замечательный материал, модель Stacked Borrows. Это экспериментальная формализация, которая пытается строго определить, какие комбинации заимствований допустимы в Rust.
Уже два года как есть ещё и Tree borrows, более разрешительная модель, которая имеет больше шансов стать окончательной.
а в чем оно расходится? Duck typing подсказывает, что это и есть интерфейсы.
Трейты позволяют описывать ассоциированные типы. Пример - Iterator из стандартной библиотеки:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// другие методы
}
У трейтов могут быть методы с реализацией по умолчанию. Пример - трейт Iterator выше: у него 76 методов, но для реализации достаточно лишь одного: next. Все остальные реализованы в терминах next.
Трейты позволяют ссылаться на тип, который его реализовывает, через Self. Пример - PartialEq:
В ООП-языках аналогичные интерфейсы или принимают верхний объект из иерархии типов (Java), или вынуждены принимать сравниваемый тип обобщённым параметром (C#) и заставляют повторять тип в описании ограничений.
Трейты могут быть реализованы не безусловно, а только при соблюдении каких-то условий. Например, трейт Serialize из популярной библиотеки serde. Он реализован для Vec только в том случае, если Serialize реализован для его элементов:
impl<T> Serialize for Vec<T>
where
T: Serialize,
{
// ...
}
Автору типа не обязательно знать о трейте для его реализации. Пример выше: Vec определён в стандартной библиотеке, но трейт Serialize и его реализация для Vec определены в serde.
Трейты могут быть реализованы для многих типов сразу. Пример: в стандартной библиотеке есть два трейта для конвертации между типами: From и Into. Реализация первого автоматически даёт реализацию второго:
impl<T, U> Into<U> for T
where
U: From<T>,
{
// ...
}
Представьте, что у вас enum имеет один из элементов MyEnum::Variant10(u8, u8, u8), а все остальные MyEnum::Variant0-9(u8), память будет выделена так:
дискриминант (по умолчанию usize т.е. 4 или 8 байт)
3 байта Итого: 11 байт на adm64
Зачем вы дезинформируете людей? Во-первых, по умолчанию размер дискриминанта выбирается минимального размера, подходящего для хранения всех вариантов. Во-вторых, если бы бы удосужились действительно проверить этот код, то увидели бы, что он печатает 4:
Что поливается говном, если никакой реализации профилей безопасности пока не существует?
Идея, что код на C++ можно корректно автоматически анализировать по функции за раз, не требуя при этом аннотаций от программиста. В исходном коде на C++ попросту недостаточно для этого информации.
И вам бы это было понятно, если бы вы таки удосужились прочитать текст по ссылке.
Вы про то, что надо знать детали реализации slice, чтобы им пользоваться? В остальном вроде всё терпимо.
Ох, если бы только это.
nil
append в nil-слайс возвращает слайс с единственным значением, но попытка вставки в nil-мапу паникует. Функцию cap можно вызвать на слайсе, но не на мапе.
Для создания выделенного в куче значения можно использовать new. С одной стороны, это избыточно, поскольку утекающий указатель на литерал значения автоматически приведёт к выделению в куче. С другой стороны, это не ортогонально, потому что де-факто ссылочные типы - слайсы, мапы и каналы - создаются через псевдо-функцию make.
Имена переменных могут затенять встроенные идентификаторы:
В языке практически отсутствует иммутабельность. Из-за этого приходится выбирать между передачей по указателю (дёшево, но функция может поменять) и передачей по значению (функция не может поменять, но потенциально дорогая операция копирования), а ещё пилить костыли. Константы могут быть только в виде чисел и строк.
У каждого типа есть нулевое значение. При создании структуры можно опустить некоторые из полей, в том числе все. Неуказанные поля получают нулевые значения. Добавил новое поле? Ищи все места самостоятельно. И не забудь, что, если структура не приватная, то внешний код тоже может такую ерунду делать.
У интерфейсов тоже есть нулевое значение, 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
}
Каналы могут быть в двух невалидных состояниях: nil и закрытом. Посылка и получение в nil-канал блокируются навечно, в то время как отправка в закрытый канал паникует, а получение из закрытого канала возвращает нулевое значение. Зачем разница в поведении - неясно.
defer вызывается в конце функции. Функции, а не лексической области видимости.
Одно ключевое слово for имеет 11 разных значений в зависимости от семантики. У четырёх из них поведение зависит от того, по какому типу итерация, но синтаксис при этом идентичный. А, и ещё можно итерироваться с одной переменной по слайсу и получить индексы вместо значений.
Для обработки ошибок используются множественные возвраты. Обычно принято, что возвращается значение и nil в случае нормального возврата, в случае ошибки же возвращается произвольное (обычно нулевое) значение и не-nil ошибка. Однако этой конвенции даже в стандартной библиотеке не везде следуют.
Если в пакете есть функция под названием init, то она будет молча вызвана при импорте этого пакета. При импорте пакета несколько раз функция init будет вызвана несколько раз.
По моему, как-то многовато надо в голове держать для так называемого простого языка.
Тем, что это - сишная идиома, проистекающая из сишной проблемы невозможности автоматически вызвать некую функцию при выходе потока исполнения из заданного лексического скоупа. Как я уже писал, в других ЯП для этого есть более адекватные средства.
В общем, property-based testing — это эксперимент, который надо уметь ставить, и его результаты тоже надо уметь обрабатывать. Об этом прекрасно рассказал автор QuickCheck в видео John Hughes — Building on developers' intuitions (...) | Lambda Days 19.
Понимаю, что статья давняя, но можете вставить ссылку на искомое видео, чтобы читателям не пришлось искать самостоятельно?
Можно, и очень легко. Ассемблер позволяет написать инструкцию, для которой поведение процессора буквально может быть не определено, а также написать код, который ломает правила языка. Через
no_mangleже можно сделать свою функцию, которая пересечётся по имени с функцией из зависимости из нативного кода, перекроет оригинальное определение с абсолютно другим ABI и разломает всю программу, если дотуда дойдёт исполнение.Потому что код так написан. Если нужно просто на печать выводить, то владение строкой можно и не принимать, а принимать лишь ссылку. Тогда можно будет и вызвать два раза подряд.
Фишка в том, что в Rust можно не делать Option и тогда нигде на отсутствующее значение проверять не надо. В других языках, где null есть, обычно он является допустимым значением для всех ссылочных типов, а записать тип, который null в число допустимых значений не включает, нельзя.
А можете привести конкретные детали синтаксиса, которые вызывают затруднение?
Уточнение: проект начали писать задолго до релиза 1.0, когда в Rust ещё был сборщик мусора и зелёные потоки, которые можно было смешивать с нативными. И да, CSS-движок Stylo оттуда в итоге в Firefox перенесли.
[citation needed]
Зачем нужно было публиковать статью про свою библиотеку во второй раз, не добавив при этом ничего полезного?
А зачем вы async-trait использовали? Асинхронные функции в трейтах уже два года как поддерживаются нативно
Уже два года как есть ещё и Tree borrows, более разрешительная модель, которая имеет больше шансов стать окончательной.
Трейты позволяют описывать ассоциированные типы. Пример - Iterator из стандартной библиотеки:
У трейтов могут быть методы с реализацией по умолчанию. Пример - трейт
Iteratorвыше: у него 76 методов, но для реализации достаточно лишь одного:next. Все остальные реализованы в терминахnext.Трейты позволяют ссылаться на тип, который его реализовывает, через
Self. Пример - PartialEq:В ООП-языках аналогичные интерфейсы или принимают верхний объект из иерархии типов (Java), или вынуждены принимать сравниваемый тип обобщённым параметром (C#) и заставляют повторять тип в описании ограничений.
Трейты могут быть реализованы не безусловно, а только при соблюдении каких-то условий. Например, трейт Serialize из популярной библиотеки serde. Он реализован для Vec только в том случае, если
Serializeреализован для его элементов:Автору типа не обязательно знать о трейте для его реализации. Пример выше:
Vecопределён в стандартной библиотеке, но трейтSerializeи его реализация дляVecопределены в serde.Трейты могут быть реализованы для многих типов сразу. Пример: в стандартной библиотеке есть два трейта для конвертации между типами: From и Into. Реализация первого автоматически даёт реализацию второго:
Как видите, отличий предостаточно.
Используйте языки с RAII
дискриминант (по умолчанию usize т.е. 4 или 8 байт)
3 байта Итого: 11 байт на adm64
Зачем вы дезинформируете людей? Во-первых, по умолчанию размер дискриминанта выбирается минимального размера, подходящего для хранения всех вариантов. Во-вторых, если бы бы удосужились действительно проверить этот код, то увидели бы, что он печатает 4:
Что обидно, поскольку конкретно приведённый фрагмент прям напрашивается на переписывание на сумм-типы
А, простите, зачем?
Идея, что код на C++ можно корректно автоматически анализировать по функции за раз, не требуя при этом аннотаций от программиста. В исходном коде на C++ попросту недостаточно для этого информации.
И вам бы это было понятно, если бы вы таки удосужились прочитать текст по ссылке.
И правильно делают, потому что профили в том виде, в котором их продвигают, не работают и принципиально работать не могут.
Ох, если бы только это.
nil
appendвnil-слайс возвращает слайс с единственным значением, но попытка вставки вnil-мапу паникует. Функциюcapможно вызвать на слайсе, но не на мапе.Для создания выделенного в куче значения можно использовать
new. С одной стороны, это избыточно, поскольку утекающий указатель на литерал значения автоматически приведёт к выделению в куче. С другой стороны, это не ортогонально, потому что де-факто ссылочные типы - слайсы, мапы и каналы - создаются через псевдо-функциюmake.Имена переменных могут затенять встроенные идентификаторы:
В языке практически отсутствует иммутабельность. Из-за этого приходится выбирать между передачей по указателю (дёшево, но функция может поменять) и передачей по значению (функция не может поменять, но потенциально дорогая операция копирования), а ещё пилить костыли. Константы могут быть только в виде чисел и строк.
У каждого типа есть нулевое значение. При создании структуры можно опустить некоторые из полей, в том числе все. Неуказанные поля получают нулевые значения. Добавил новое поле? Ищи все места самостоятельно. И не забудь, что, если структура не приватная, то внешний код тоже может такую ерунду делать.
У интерфейсов тоже есть нулевое значение,
nil, и это отличается от интерфейса, который хранитnil-значение:Переменные цикла переиспользуются между итерациями, что в своё время привело к отзыву трёх миллионов сертификатов Let's Encrypt. Сейчас это поправили, но более разумное поведение, ЕМНИП, всё ещё opt-in.
Каналы могут быть в двух невалидных состояниях:
nilи закрытом. Посылка и получение вnil-канал блокируются навечно, в то время как отправка в закрытый канал паникует, а получение из закрытого канала возвращает нулевое значение. Зачем разница в поведении - неясно.deferвызывается в конце функции. Функции, а не лексической области видимости.Одно ключевое слово
forимеет 11 разных значений в зависимости от семантики. У четырёх из них поведение зависит от того, по какому типу итерация, но синтаксис при этом идентичный. А, и ещё можно итерироваться с одной переменной по слайсу и получить индексы вместо значений.Для обработки ошибок используются множественные возвраты. Обычно принято, что возвращается значение и
nilв случае нормального возврата, в случае ошибки же возвращается произвольное (обычно нулевое) значение и не-nilошибка. Однако этой конвенции даже в стандартной библиотеке не везде следуют.Если в пакете есть функция под названием
init, то она будет молча вызвана при импорте этого пакета. При импорте пакета несколько раз функцияinitбудет вызвана несколько раз.По моему, как-то многовато надо в голове держать для так называемого простого языка.
Тем, что это - сишная идиома, проистекающая из сишной проблемы невозможности автоматически вызвать некую функцию при выходе потока исполнения из заданного лексического скоупа. Как я уже писал, в других ЯП для этого есть более адекватные средства.
Только
[[maybe_unused]]ещё докинуть, чтобы компилятор на определение гуарда не ругался.А этот антипаттерн от языка не зависит
Понимаю, что статья давняя, но можете вставить ссылку на искомое видео, чтобы читателям не пришлось искать самостоятельно?