Как стать автором
Обновить

Комментарии 12

tokio::spawn вообще то боксит футуру, там все понятно. А never type это из теории типов и означает совсем другое, чем в статье написано.
Гуглите ненаселенные типы или bottom.

Немного странная статья. Вроде все по делу написано, но достаточно странное восприятие получается. Ну то есть это как считать поведение объектов солнечной системы по эпициклам — вроде можно, но трудно согласиться что это все планеты кружатся вокруг Земли. Объяснение что они все крутятся вокруг Солнца — проще и понятнее. По пунктам:


  1. Тип данных "!" просит выйти из текущего блока кода.

    ! это never-type. Он возвращает боттом, и ни разу не требует ниоткуда выходить. Например loop {} имеет тип !, но конечно же никакого ретурна из процесса не делает.
    Полезно это, когда общий интерфейс разрешает ошибку, а мы в своей реализации точно знаем что ошибки не будет. Ну например есть у нас тайпкласс


    trait MyFileSystem {
      type TError;
    
      fn read_file(path: Path) -> Result<FileMetadata, Self::TError>
    }

    И пишем мы свою стаб-реализацию для тестов, в которой ошибки никогда не будет. Как нам это выразить в коде? Ну, чтобы анврапы не писать, а то если мы стаб вдруг поменяем на тот который умеет в ошибки, вместо полезной ошибки компиляции мы получим анврап. А вот так:


    struct TestFilesystem;
    
    impl MyFileSystem {
      type TError = !;
    
      fn read_file(path: Path) -> Result<FileMetadata, Self::TError> {
        Ok(FileMetadata::new("test", "rwxrwxrwx"))
      }
    }

    В этом случае при попытке сделать read_file компилятор разрешит сразу матчить на ок:


    let Ok(metadata) = test_filesystem.read_file(path);

    Офк если ошибка возможна то так писать нельзя и нужно расписывать действия на случай ошибки. Но т.к. мы явно написали, что этого не будет — компилятор разрешает нам этого не делать.


  2. Сразу скажу, что этот вопрос скорее дискуссионный. Здесь я не буду рассказывать о малоизвестной вещи, речь пойдет о странно реализованном полиморфизме.

    В расте совершенно обычный полиморфизм. Просто нужно понимать, что есть структуры, а есть трейты(тайпклассы). impl Trait возвращает анонимный объект, реализующий интерфейс. Да, компилятор его знает, но вам не скажет. Единственный способ вернуть такое замыкание — использовать impl, который называется "экзистенциальный тип". Наравне с универсальным типом (который мы пишем как <T>) экзистенциальный тип является квантификатором на уровне типов. Но если универсальный говорит для любых Т есть функция Foo(T) то второй существует некоторый T реализующий интерфейс Bar, который является результатом функции Foo() -> T. Откуда собственно он и называется экзистенциальный — потому что мы доказываем, что такой тип существует.


    Fn тут не уникален, это касается любых типов, которые нельзя явно указать: итераторы, футуры...


    Фишка в том, что Fn — это не структура, это трейт. Точно так же как нельзя просто так вернуть Clone и нужно писать:


    use std::any::type_name;
    fn type_of<T>(x: T) -> &'static str {
        type_name::<T>()
    }
    
    fn callback() -> impl Clone {
        |a: f32| {
          a*2.  
        }
    }
    
    fn main() {
        let x = callback();
        println!("{}", type_of(x));
    }

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


    Чтобы вернуть конкретный (не экзистенциальный) тип в случае Fn можно скастовать его к лямбда-типу: это такое замыкание, которое не имеет параметров.


    use std::any::type_name;
    fn type_of<T>(x: T) -> &'static str {
        type_name::<T>()
    }
    
    fn callback() -> fn(f32) -> f32 {
        |a| {
          a*2.  
        }
    }
    
    fn main() {
        let x = callback();
        println!("{}", type_of(x));
    }

    Почему оно работает? Потому что в общем случае лямбды могут занимать разное место в памяти. Лямбда захватывающае 2 булеана весит 2 байта, а один i32 — 4 байта. Соответственно они никак не могут быть одного типа: у них разное количество разных членов разных типов. Единственное что у них общее — у них всех есть функция apply чтобы вызвать некоторую логику. И только лямбды занимающие 0 байт (не замыкающие никаких переменных) имеют единственный тип fn(...) -> ... реализующую интерфейс Fn(...) -> ....


  3. Почему код не выполнится, а тот, что выше, выполнится?

    Потомому что компилятор не может вывести самый общий тип для выражений за боксом в данном случае (не уверен что это вообще реально без Хиндли-милнера, которого в расте нет), поэтому ему нужно помочь:


    use core::future::Future;
    fn foo() {
        let v: Vec<Box<dyn Future<Output = ()>>> = vec![
            Box::new(async{}),
            Box::new(async{
                let cb = |x| x*2.;
                let val = cb(1f32);
            })
        ];
    }

Ну то есть это как считать поведение объектов солнечной системы по эпициклам — вроде можно, но трудно согласиться что это все планеты кружатся вокруг Земли. Объяснение что они все крутятся вокруг Солнца — проще и понятнее.

Объяснение-то, может быть, и проще (но и тут можно вспомнить Холмса), но если надо именно считать, то это как раз проще "по Птолемею" делать. Сомневаюсь, что обычный школьник может эфемериды планет рассчитать самостоятельно по Кеплеру или Ньютону.


Ну т.е. аналогия такая… обоюдоострая.


PS. Вот если надо по трем наблюдениям получить параметры орбиты астероида, несущегося к Земле — вот тут да, Альмагест не по может.

Чтобы вернуть конкретный (не экзистенциальный) тип в случае Fn можно скастовать его к лямбда-типу: это такое замыкание, которое не имеет параметров.

Это функциональный указатель. Фактически — адрес исполняемого куска кода. Обычные функции тоже можно привести к функциональному указателю.

Честно говоря, второй заголовок я написал будучи в небольшом заблуждении. Для меня стало большим открытием, что в расте есть анонимные классы и что код
trait Identifier<'a>{
    fn identify(&self) -> &'a str;
}

fn main(){
    let implementor: Box<dyn Identifier> = {
        struct Anonymous{}
        impl<'a> Identifier<'a> for Anonymous{
            fn identify(&self) -> &'a str{
                "I have no id..."
            }
        }
        
        Box::new(Anonymous{})
    };
    
    let id = implementor.identify();
}

компилируется. Спасибо за развёрнутый ответ, стало намного понятнее.

Это не класс, а структура, и она не анонимный, у неё вполне себе есть имя. Тот, что Box<Anonymous> приводится к Box<dyn Identifier>, из которого вытащить тип уже проблематично — это другой вопрос

Это не класс, а структура

Это просто придирка, что в языке нет классов или правда есть какая-то разница?

Структуры нельзя наследовать друг от друга, как минимум.

В разных языках по-разному. В плюсах — можно.

Честно говоря, не понял ответ.


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

Просто уточнение, чтобы заодно воспользоваться усилителем "И не Х, а Х".


Можно было просто сказать, что анонимных классов в расте нет.

Пока ! не стабилизирован, но его можно либо определить самостоятельно (как пустой enum) либо воспользоваться крейтом never.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории