[Программирование, Rust] Размышления о Rust
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Всем привет. Не сразу, но я полюбил Rust. И эта любовь привела меня в бескрайние моря лоулевельного кода. О том, что мне удалось найти - под катом.
Секретный тип данныхЕсли вы читали Rust Book, то наверняка помните похожий код-сниппет:
fn unwrap<T>(option: Option<T>) -> T{
let unwrapped = match option{
Some(val) => val,
None => panic!("This cannot be None!")
};
return unwrapped;
}
fn main() {
let unwrapped = unwrap(Some(0));
}
[url=https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&code=fn%20unwrap%3CT%3E%28option:%20Option%3CT%3E%29%20-%3E%20T{let%20unwrap%20=%20match%20option{Some%28val%29%20=%3E%20val,None%20=%3E%20panic!%28%22This%20cannot%20be%20None!%22%29};return%20unwrap;}fn%20main%28%29%20{let%20unwrap%20=%20unwrap%28Some%280%29%29;}]Проверить[/url]Конечно, здесь нет ничего необычного. Возвращаем значение внутри Option, если оно есть, либо вызываем завершение процесса с помощью макроса panic!. Но задумывались ли вы, почему этот код компилируется? Как компилятор понимает, что функция, возвращающая T, может вернуть... это?На самом деле, всё очень просто - макрос panic возвращает тип данных "!". ДокументацияТип данных "!" просит выйти из текущего блока кода. Как это использовать? Сами разработчики языка предлагают такой вариант:
#![feature(never_type)]
use std::convert::TryInto;
#[derive(Debug)]
enum ConnectionError{
BrokenPipe,
BadId,
Other
}
struct Client;
struct Request;
struct Response;
impl Request{
pub fn build_response(&self) -> Response{
Response
}
}
fn get_request(id: i32) -> Result<(Client, Request), ConnectionError>{
match id % 2 == 0{
true => {
Ok((Client, Request))
},
false => {
Err(ConnectionError::BadId)
}
}
}
fn init_server() -> Result<!, ConnectionError>{
loop {
let (client, request) = get_request(5i32)?;
let resp = request.build_response();
};
}
fn main() {
let x: ! = init_server().unwrap();
}
[url=https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&code=#!%5Bfeature%28never_type%29%5Duse%20std::convert::TryInto;#%5Bderive%28Debug%29%5Denum%20ConnectionError{BrokenPipe,BadId,Other}struct%20Client;struct%20Request;struct%20Response;impl%20Request{pub%20fn%20build_response%28&self%29%20-%3E%20Response{Response}}fn%20get_request%28id:%20i32%29%20-%3E%20Result%3C%28Client,%20Request%29,%20ConnectionError%3E{match%20id%20%%202%20==%200{true%20=%3E%20{Ok%28%28Client,%20Request%29%29},false%20=%3E%20{Err%28ConnectionError::BadId%29}}}fn%20init_server%28%29%20-%3E%20Result%3C!,%20ConnectionError%3E{loop%20{let%20%28client,%20request%29%20=%20get_request%284i32%29?;let%20resp%20=%20request.build_response%28%29;};}fn%20main%28%29%20{let%20x:%20!%20=%20init_server%28%29.unwrap%28%29;}]Проверить[/url]Однако, эта конструкция, работающая только в nightly билдах, спокойно превращается в обычный код заменой "!" на пустой тип данных "()":
fn init_server() -> Result<(), ConnectionError>{
loop {
let (client, request) = get_request(5i32)?;
let resp = request.build_response();
};
}
fn main() {
let x = init_server().unwrap();
}
В чём же разница? Всё очень просто, в первом примере мы не сможем полностью выполнить код:
fn main() {
match init_server(){
Ok(v) => { println!("unreachable? {:?}", v); },
Err(_) => {}
};
}
Компилятор любезно сообщит, что ветка Ok(v) - недостижима. Разумеется, это не помешает ему запустить программу, однако мне хотелось бы обозначить такую интересную особенность. Понятно, что она была бы недостижима и во втором примере, однако если его скомпилировать, то сообщения о недостижимом коде не будет.Почему так происходит? Потому что то, что примет значение v в данном сниппете буквально означает "выход". "!" возвращается, когда вы пишете break, continue или std::process::exit. И, внимание, вопрос. Зачем нужна #![feature(never_type)] ? С тех пор, как я узнал об этом типе данных, я думал, где его можно применить. И такого места, кажется, нет. Во всех случаях вы будете использовать panic, expect, todo или unimplemented. К чему нужен "!"?Странные интерфейсыСразу скажу, что этот вопрос скорее дискуссионный. Здесь я не буду рассказывать о малоизвестной вещи, речь пойдет о странно реализованном полиморфизме.В Rust есть такой интерфейс (трейт - скорее абстрактный класс, но лично мне удобнее называть его интерфейсом) Fn. И вроде бы с ним всё просто - все функции и лямбда-выражения ("closures" или "замыкания", если угодно), принимающие иммутабельные входные значения, его реализуют. В чём тут подвох?Дело в том, что, как мы знаем, нельзя создать переменную типа данных impl Trait. Но проблема в том, что такой тип данных можно вернуть из функции, и это вызывает вопросы...
use std::any::type_name;
fn type_of<T>(x: T) -> &'static str {
type_name::<T>()
}
fn callback() -> impl Fn(f32) -> f32{
|a| {
a*2.
}
}
fn main() {
let x = callback();
println!("{}", type_of(x));
}
Вывод будет такой: playground::callback::{{closure}} . И вот, казалось бы, переменная х имеет тип данных impl Fn(f32) -> f32, вот только если мы об этом явно напишем, то код не скомпилируется. Как мы знаем, чтобы хранить trait object, нужно использовать ключевое слово dyn. Но вот незадача - компилятор не знает, сколько памяти будет занимать этот trait object, поэтому необходимо такие вещи класть в кучу с помощью Box:
fn main() {
let x: Box<dyn Fn(f32) -> f32> = Box::new(callback());
println!("{}", type_of(x));
}
Но тогда и вывод поменяется: alloc::boxed::Box<dyn core::ops::function::Fn<(f32,)>+Output = f32>Я это понимаю так: компилятор знает, что это за имплементация, поэтому может рассчитать количество необходимой памяти. Однако мне до сих пор не даёт покоя мысль, что такой код работает:
use tokio; // 1.0.2
use tokio::task::JoinError;
use futures::prelude::*; // 0.3.12
async fn job1(){}
async fn job2(){
for i in 0..5{}
}
async fn job() -> Vec<impl Future<Output = Result<(), JoinError>>>{
vec![
tokio::spawn(async move{
job1().await;
}),
tokio::spawn(async move{
job2().await;
})]
}
#[tokio::main]
async fn main() {
let mut v = job();
}
С точки зрения логики компилятора, тут нет проблемы - tokio::spawn создаёт структуруtokio::task::JoinHandle. Да, JoinHandle - это одна и та же структура, она принимает футуру, которая создаётся блоком async{} , однако почему таски, содержащие разные async-блоки, интерпретируются компилятором как одна и та же реализация? Почему код
let v = vec![
Box::new(async{}),
Box::new(async{
let cb = |x| x*2.;
let val = cb(1f32);
})
];
не выполнится, а тот, что выше, выполнится? Ведь мы передаём разные реализации футур. Поделитесь в комментариях, если у вас есть мысли по этому поводу. Я честно с умным видом полчаса изучал исходники, но так и не понял.ЗаключениеRust, каким бы хорошим ни был, порой заставляет крепко задуматься. Почему retain не меняет capacity? Почему функциональные исчисления сделали ленивыми? Почему cargo создаёт странные папки с хеш-суммами на каждый случай жизни, вместо того, чтобы собрать одни и те же библиотеки один раз (хотя, справедливости ради, это не проблема самого языка)? Как бы то ни было, если писать на плюсах - это стрелять себе в ногу, то писать на расте - это пытаться стрелять себе в ногу (и не дай бог в проекте вы используете ffi, тогда попытки могут оказаться вполне успешными). Цель этой статьи - попытаться углубиться внутрь языка, понять, как он работает изнутри, ведь, как известно, любить можно только того, кого понимаешь.
===========
Источник:
habr.com
===========
Похожие новости:
- [Программирование] Mutation Driven Development
- [Python, Программирование, Django] Конвертеры маршрутов в Django 2.0+ (path converters)
- [Программирование, Go] Создаем бессерверное приложение с помощью Azure Functions и Go (перевод)
- [Системное администрирование, Программирование, Кодобред, *nix, Оболочки] Консольный менеджер сертификатов для JKS/PCKS12
- [Open source, Программирование, Совершенный код, C++] Исследование COVID-19 и неинициализированная переменная
- [Open source, Программирование, Совершенный код, C++] COVID-19 Research and Uninitialized Variable
- [Программирование, C++] C++17. Функция стандартной библиотеки std::launder и задача девиртуализации
- [C++, Программирование микроконтроллеров] Достучаться до небес, или FSM на шаблонах
- [Программирование, .NET, C#] Шпион под прикрытием: проверяем исходный код ILSpy с помощью PVS-Studio
- [Программирование, .NET, C#] A Spy Undercover: PVS-Studio to Check ILSpy Source Code
Теги для поиска: #_programmirovanie (Программирование), #_rust, #_rust, #_tokio/futures, #_kompiljatory (компиляторы), #_programmirovanie (
Программирование
), #_rust
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:22
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Всем привет. Не сразу, но я полюбил Rust. И эта любовь привела меня в бескрайние моря лоулевельного кода. О том, что мне удалось найти - под катом. Секретный тип данныхЕсли вы читали Rust Book, то наверняка помните похожий код-сниппет: fn unwrap<T>(option: Option<T>) -> T{
let unwrapped = match option{ Some(val) => val, None => panic!("This cannot be None!") }; return unwrapped; } fn main() { let unwrapped = unwrap(Some(0)); } #![feature(never_type)]
use std::convert::TryInto; #[derive(Debug)] enum ConnectionError{ BrokenPipe, BadId, Other } struct Client; struct Request; struct Response; impl Request{ pub fn build_response(&self) -> Response{ Response } } fn get_request(id: i32) -> Result<(Client, Request), ConnectionError>{ match id % 2 == 0{ true => { Ok((Client, Request)) }, false => { Err(ConnectionError::BadId) } } } fn init_server() -> Result<!, ConnectionError>{ loop { let (client, request) = get_request(5i32)?; let resp = request.build_response(); }; } fn main() { let x: ! = init_server().unwrap(); } fn init_server() -> Result<(), ConnectionError>{
loop { let (client, request) = get_request(5i32)?; let resp = request.build_response(); }; } fn main() { let x = init_server().unwrap(); } fn main() {
match init_server(){ Ok(v) => { println!("unreachable? {:?}", v); }, Err(_) => {} }; } use std::any::type_name;
fn type_of<T>(x: T) -> &'static str { type_name::<T>() } fn callback() -> impl Fn(f32) -> f32{ |a| { a*2. } } fn main() { let x = callback(); println!("{}", type_of(x)); } fn main() {
let x: Box<dyn Fn(f32) -> f32> = Box::new(callback()); println!("{}", type_of(x)); } use tokio; // 1.0.2
use tokio::task::JoinError; use futures::prelude::*; // 0.3.12 async fn job1(){} async fn job2(){ for i in 0..5{} } async fn job() -> Vec<impl Future<Output = Result<(), JoinError>>>{ vec![ tokio::spawn(async move{ job1().await; }), tokio::spawn(async move{ job2().await; })] } #[tokio::main] async fn main() { let mut v = job(); } let v = vec![
Box::new(async{}), Box::new(async{ let cb = |x| x*2.; let val = cb(1f32); }) ]; =========== Источник: habr.com =========== Похожие новости:
Программирование ), #_rust |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 22-Ноя 18:22
Часовой пояс: UTC + 5