Forwarded from Matwey Kornilov
поребрик выглядит как дельта-функция, а бордюр как функция хевисайда
#prog #rust #моё
В Rust есть много хороших вещей. Одна из них — функции как первоклассные значения. Мы можем передать функцию как аргумент, возвращать функцию из функции и даже хранить функции в коллекциях. К сожалению, нацеленность Rust на абстракции нулевой (дополнительной) стоимости создаёт некоторые препятствия к широкому применению этой возможности и часто создаёт вопросы у новичков. Какие могут быть проблемы? Ну... Давайте просто поиграем с функциями, а проблемы возникнут сами 😈.
Сделаем функцию, которая будет принимать на вход два аргумента: функцию из числа в число и число, и которая будет просто применять функцию к этому числу. Неискушённый человек, только постигающий Rust, может написать следующее:
Да, но это не просто функция, это замыкание, т. е. функция, использующая данные за пределами е определения. Для того, чтобы вызвать это замыкание, требуется не только знать адрес, по которому расположен код функции, но и каким-то образом передать захваченные данные. Для каждой лямбда-функции компилятор Rust создаёт создаёт свой собственный уникальный тип. Поля этого типа — это данные, которые захватываются замыканием, в данном случае — поле под значение
Но погодите, почему же удалось вызвать
Потому что лямбда-функция
Это всё, конечно, очень интересно, но как мне всё-таки вызвать
Очевидно, поменяв тип
Ну, не совсем:
В Rust есть много хороших вещей. Одна из них — функции как первоклассные значения. Мы можем передать функцию как аргумент, возвращать функцию из функции и даже хранить функции в коллекциях. К сожалению, нацеленность Rust на абстракции нулевой (дополнительной) стоимости создаёт некоторые препятствия к широкому применению этой возможности и часто создаёт вопросы у новичков. Какие могут быть проблемы? Ну... Давайте просто поиграем с функциями, а проблемы возникнут сами 😈.
Сделаем функцию, которая будет принимать на вход два аргумента: функцию из числа в число и число, и которая будет просто применять функцию к этому числу. Неискушённый человек, только постигающий Rust, может написать следующее:
// vvvvvvvvvvvvvv - это же функция, верно?Попробуем её применить:
fn apply(func: fn(u32) -> u32, x: u32) -> u32 {
func(x)
}
fn add_one(x: u32) -> u32 {Этот код выводит "4", как и ожидается. Но постойте-ка, у нас же есть замечательный синтаксис для лямбда-функций! Может, применить его?
x + 1
}
fn main() {
println!("{}", apply(add_one, 3));
}
fn main() {Этот код тоже выводит "4". Что ж, давайте немного усложним код:
println!("{}", apply(|x| x + 1, 3));
}
fn main() {И этот код выводит... А не, не выводит, он не компилируется:
let one = 1;
println!("{}", apply(|x| x + one, 3));
}
error[E0308]: mismatched typesНо ведь это же функция!
--> src/main.rs:8:26
|
8 | println!("{}", apply(|x| x + one, 3));
| ^^^^^^^^^^^ expected fn pointer, found closure
|
= note: expected fn pointer `fn(u32) -> u32`
found closure `[closure@src/main.rs:8:26: 8:37 one:_]`
Да, но это не просто функция, это замыкание, т. е. функция, использующая данные за пределами е определения. Для того, чтобы вызвать это замыкание, требуется не только знать адрес, по которому расположен код функции, но и каким-то образом передать захваченные данные. Для каждой лямбда-функции компилятор Rust создаёт создаёт свой собственный уникальный тип. Поля этого типа — это данные, которые захватываются замыканием, в данном случае — поле под значение
one
. Тип же fn(u32) -> u32
— это функциональный указатель (function pointer), фактически, просто адрес в памяти, по которому располагается код функции. Неудивительно, что apply
не удалось вызвать — это разные типы с разными размерами!Но погодите, почему же удалось вызвать
apply
с аргументом |x| x + 1
?Потому что лямбда-функция
|x| x + 1
не захватывает никаких переменных из окружения, а такие замыкания могут быть приведены к функциональному указателю начиная с версии Rust 1.19. Такое приведение может сработать в ряде различных мест, в число которых входит и аргумент функции при вызове.Это всё, конечно, очень интересно, но как мне всё-таки вызвать
apply
с |x| x + one
?Очевидно, поменяв тип
apply
. Мы не можем дать типу замыканию имя, поэтому правильным решением будет сделать apply
обобщённой функцией:fn apply<F>(func: F, x: u32) -> u32Что тут происходит? Мы ввели обобщённый параметр
where
F: Fn(u32) -> u32,
{
func(x)
}
F
и добавили на него ограничение Fn(u32) -> u32
. Это означает, что значение типа F
можно вызвать как функцию с одним аргументом u32
, причём неограниченное число раз. Теперь apply
можно вызвать в любом варианте:fn main() {Отлично! Значит, замыкания имеют разные типы, а у обычных функций с одинаковыми сигнатурами типы одинаковые
println!("{}", apply(add_one, 3));
println!("{}", apply(|x| x + 1, 3));
let one = 1;
println!("{}", apply(|x| x + one, 3));
}
Ну, не совсем:
type Pair<T> = (T, T);Этот код не компилируется:
fn add_one(x: u32) -> u32 {
x + 1
}
fn add_two(x: u32) -> u32 {
x + 2
}
fn main() {
let funcs: Pair<_> = (add_one, add_two);
}
error[E0308]: mismatched types
--> src/main.rs:12:36
|
12 | let funcs: Pair<_> = (add_one, add_two);
| ^^^^^^^ expected fn item, found a different fn item
|
= note: expected fn item `fn(_) -> _ {add_one}`
found fn item `fn(_) -> _ {add_two}`
Wikipedia
Closure (computer programming)
technique for creating lexically scoped first class functions
Так, а что тут не так? И почему не совпадают типы?
Дело в том, что у каждой функции — не только у замыканий — в Rust свой собственный уникальный тип. Здесь мы попытались сохранить значения двух разных типов в паре одинаковых типов, поэтому наш код не компилируется.
Зачем так сложно? В этом же нет никакого смысла!
Вообще-то есть. Компилятор эксплуатирует тот факт, что тип населён единственным значением, и потому не хранит и не передаёт адрес функции, а вставляет его по месту вызова. Таким образом, компилятору предоставляется больше возможностей для встраивания (inlining) функций. Также это влияет на структуры, которые хранят в себе функции: если тип не указан явно как функциональный указатель, обычная функция вообще не занимает места в памяти. Функциональный указатель же занимает столько же места, сколько и указатель:
Просто хранить разнородную пару.
А если нам принципиально, чтобы элементы пары имели одинаковый тип?
В таком случае нам надо привести значения к одинаковому типу. Это можно сделать так:
А вот про это я расскажу в следующий раз.
Дело в том, что у каждой функции — не только у замыканий — в Rust свой собственный уникальный тип. Здесь мы попытались сохранить значения двух разных типов в паре одинаковых типов, поэтому наш код не компилируется.
Зачем так сложно? В этом же нет никакого смысла!
Вообще-то есть. Компилятор эксплуатирует тот факт, что тип населён единственным значением, и потому не хранит и не передаёт адрес функции, а вставляет его по месту вызова. Таким образом, компилятору предоставляется больше возможностей для встраивания (inlining) функций. Также это влияет на структуры, которые хранят в себе функции: если тип не указан явно как функциональный указатель, обычная функция вообще не занимает места в памяти. Функциональный указатель же занимает столько же места, сколько и указатель:
fn main() {Кстати, на замыкания это тоже распространяется: значение замыкания хранит в себе только захваченные данные, поэтому размер замыкания равен (с поправкой на выравнивание) размеру захваченных данных. Если замыкание ничего не захватывает, то у него нулевой размер:
assert_eq!(std::mem::size_of_val(&add_one), 0);
let add_one_ptr: fn(u32) -> u32 = add_one;
assert_eq!(
std::mem::size_of_val(&add_one_ptr),
std::mem::size_of::<usize>()
);
}
fn main() {Теперь я вижу, что в этом действительно есть смысл. И всё-таки, как скомпилировать тот пример с парой функций?
assert_eq!(std::mem::size_of_val(&|x: u32| x + 1), 0);
}
Просто хранить разнородную пару.
А если нам принципиально, чтобы элементы пары имели одинаковый тип?
В таком случае нам надо привести значения к одинаковому типу. Это можно сделать так:
let funcs: Pair<fn(_) -> _> = (add_one, add_two);Обратите внимание, выписывать тип точно в этом случае не нужно, достаточно сказать, что это function pointer. Другой способ — явно привести одно из значений в паре:
let funcs: Pair<_> = (add_one as fn(_) -> _, add_two);Второе значение будет неявно приведено (coerced) к нужному типу. В принципе, ничто не мешает написать явный каст у обоих значений, но надобности в этом в данном примере нет. Кстати, если мы сделаем массив функций, то никаких кастов делать не придётся, rustc сам уравняет типы до функционального указателя:
let arr = [add_one, add_two];А что, если замыкание возвращает захваченное значение? Если это значение не копируемо, то замыкание можно вызвать только один раз...
А вот про это я расскажу в следующий раз.
#prog #amazingopensource
Если вы когда-нибудь хотели сделать свой блог, но не знали, как сделать комментарии (или не хотели подключать жирный Disqus), то этот вариант может подойти: комментарии, работающие поверх issue в GitHub.
utteranc.es
Если вы когда-нибудь хотели сделать свой блог, но не знали, как сделать комментарии (или не хотели подключать жирный Disqus), то этот вариант может подойти: комментарии, работающие поверх issue в GitHub.
utteranc.es
Forwarded from мне не нравится реальность
Есть некоторое http-api. Я пишу для него враппер на расте. В апи один из параметров метода — число от 1 до 100000. Вопрос: как лучше отобразить отобразить этот параметр в либе?
Final Results
27%
u32
3%
NonZeroU32
63%
new-type который будет проверять что он >= 1 && <= 100000
8%
(свой вариант)
#blogrecommendation
Человек пишет на Rust... Много всего. В частности, недетерминированный парсер для естественного языка — и это его дипломная работа. Если честно, я даже немного завидую его продуктивности.
t.iss.one/optozorax_dev/184
Человек пишет на Rust... Много всего. В частности, недетерминированный парсер для естественного языка — и это его дипломная работа. Если честно, я даже немного завидую его продуктивности.
t.iss.one/optozorax_dev/184
Telegram
dev optozorax
А мой парсер всё обрастает возможностями. Теперь можно задавать как будут изменяться "вероятности" распаршенного правила.
UPD: это скорее не вероятности, а веса.
Мы всё-же парсим естественный язык достаточно приближённо, значит некоторые конструкции весьма…
UPD: это скорее не вероятности, а веса.
Мы всё-же парсим естественный язык достаточно приближённо, значит некоторые конструкции весьма…
Forwarded from red75prime
unsafe не значит, что там что-то не так. Это значит, что мы знаем что-то, что не знает компилятор. Или думаем, что знаем
#prog #article
Реальность такова, что среди существующих программ полно уязвимых, допускающих использование в злонамеренных целях. Огромное количество подобных уязвимостей проистекает из некорректного парсинга входных данных. Конечно, легко сказать: "Эй, программисты, будьте внимательнее", но практика показывает, что подобный метод работает довольно фигово. В данной статье перечислены основные проблемы, возникающие при обработке входных данных, вместе с более-менее конкретными указаниями на то, как их исправить.
https://langsec.org/papers/langsec-cwes-secdev2016.pdf
Реальность такова, что среди существующих программ полно уязвимых, допускающих использование в злонамеренных целях. Огромное количество подобных уязвимостей проистекает из некорректного парсинга входных данных. Конечно, легко сказать: "Эй, программисты, будьте внимательнее", но практика показывает, что подобный метод работает довольно фигово. В данной статье перечислены основные проблемы, возникающие при обработке входных данных, вместе с более-менее конкретными указаниями на то, как их исправить.
https://langsec.org/papers/langsec-cwes-secdev2016.pdf
#prog #rust #article
Недавно в Rust-сообществе поднялся шум по поводу недавней статьилодочника WithoutBoats, в которой он приводит аргументы за автоматическое оборачивание в
Недавно в Rust-сообществе поднялся шум по поводу недавней статьи
Ok
значений, возвращаемых из функций с типом возврата Result
. В ней представлены несколько аргументов "за" различной степени убедительности и опровергнуты несколько откровенно слабых аргументов "против". Один из ответов на эту статью — Mental models about Ok-Wrapping, в которой приведены реальные сложности, которые могут возникнуть с на практике с подобной реализацией.