Как проверить, что T или Any реализует черту в языке Rust

Я только начинаю работать с Rust, и мне нужна помощь в понимании проверки типов в языке. Я пытаюсь внедрить регистратор активности для регистрации и регистрации продолжительности выполнения и результата функции. Вот упрощенная реализация:

struct Activity {
    name: String,
    start_time: std::time::Instant,
}

impl Activity {
    fn begin(name: &str) -> Self {
        println!("Activity started: {}", name);
        Activity {
            name: name.to_string(),
            start_time: std::time::Instant::now(),
        }
    }

    fn end_ok(&self) {
        let duration = self.start_time.elapsed().as_millis();
        println!("Activity ended: {}, Status: Success, Duration: {}", self.name, duration);
    }

    fn end_error<T: ToString>(&self, error: &T) {
        let duration = self.start_time.elapsed().as_millis();
        println!(
            "Activity ended: {}, Status: Fail, Duration: {}, Message: {}",
            self.name,
            duration,
            error.to_string()
        );
    }
}

type MyResult = Result<(), String>;

fn test1() -> Result<(), Box<dyn std::error::Error>> {
    test2()?;
    Ok(())
}

fn test2() -> MyResult {
    Err("oops".to_string())
}

fn main() {
    let activity = Activity::begin("TestActivity");

    match test1() {
        Ok(_) => activity.end_ok(),
        Err(error) => activity.end_error(&error),
    }
}

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

Я хочу узнать, что возвращает функциональный блок. Если это Result<_, _> и Err, я хочу вызвать end_error(error); иначе я хочу вызвать end_ok(). Вот пример моей попытки макроса:

extern crate proc_macro;

use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, ItemFn, LitStr, ReturnType};

#[proc_macro_attribute]
pub fn activity_logger(attr: TokenStream, item: TokenStream) -> TokenStream {
    let logger_name = parse_macro_input!(attr as LitStr).value();
    let input_fn = parse_macro_input!(item as ItemFn);
    let fn_name = &input_fn.sig.ident;
    let fn_block = &input_fn.block;
    let fn_return_type = &input_fn.sig.output;
    
    let output = match fn_return_type {
        ReturnType::Type(_, _) => quote! {
            fn #fn_name() #fn_return_type {
                let activity = Activity::begin(#logger_name);

                let result = (|| #fn_block)();
                
                // C# like pseudo code start
                if result is Result<_, _> {
                    match result {
                        Ok(_) => activity.end_ok(),
                        Err(error) => activity.end_error(error),
                    }
                } else {
                    activity.end_ok();
                }
                // C# like pseudo code end

                result
            }
        },
        ReturnType::Default => quote! {
            fn #fn_name() {
                let activity = Activity::begin(#logger_name);

                #fn_block
                
                activity.end_ok();
            }
        },
    };

    output.into()
}

Есть ли способ определить во время выполнения, реализует ли объект черту Try или является Result<_, _>? Или макрос может в этом помочь? Я ценю любой совет!

Единственный найденный мною способ определить тип возвращаемого значения — это извлечь имя типа с помощью ReturnType::Type(_, ty) или std::any::type_name, но это не идеальный вариант, поскольку тип результата можно переименовать.

Я пытаюсь решить это с помощью черт

  • но Rust не допускает такой код из-за неоднозначности реализации:
    trait IsResult {
        const IS_RESULT: bool;
    }
    
    impl<T, E> IsResult for Result<T, E> {
        const IS_RESULT: bool = true;
    }
    
    impl<T> IsResult for T {
        const IS_RESULT: bool = false;
    }
    
  • но особенности Rust не похожи на интерфейсы других языков, и этот подход не сработал:
    trait Loggable {
        fn can_log(&self) -> bool;
    }
    
    trait LoggableResult: Loggable {
        fn can_log(&self) -> bool;
    }
    
    impl<T> Loggable for T {
        fn can_log(&self) -> bool {
            false
        }
    }
    
    impl<T, E> LoggableResult for Result<T, E> {
        fn can_log(&self) -> bool {
            true
        }
    }
    
    fn can_log(value: &dyn Loggable) -> bool {
        value.can_log()
    }
    
    fn test() {
        let test1_result = 123;
        let test1 = can_log(&test1_result); // false
        let test2_result: Result<(), String> = Result::Err("oops".to_string());
        let test2 = can_log(&test2_result); // false
    }
    

Нельзя, ищите specialization, это пока не реализовано.

cafce25 05.09.2024 14:20

Макросы не имеют доступа к информации о типе, поскольку они раскрываются перед проверкой типа.

jthulhu 05.09.2024 14:47
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
2
66
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это действительно возможно! (см. https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md) Я создам декларативный макрос, чтобы продемонстрировать:

trait TypeDetector {
    fn type_detect(&self, activity: &Activity);
}

impl<A, B: ToString> TypeDetector for Result<A, B> {
    fn type_detect(&self, activity: &Activity) {
        match self {
            Ok(_) => {
                activity.end_ok();
            }
            Err(error) => {
                activity.end_error(error);
            }
        }
    }
}

impl<A> TypeDetector for &A {
    fn type_detect(&self, activity: &Activity) {
        activity.end_ok();
    }
}

macro_rules! activity_logger {
    ($log_name:expr, $vis:vis fn $name:ident() -> $out:ty {$($body:tt)*}) => {
        $vis fn $name() -> $out {
            let activity = Activity::begin($log_name);
            let result = (||{$($body)*})();
            (&result).type_detect(&activity);
            result
        }
    }
}

Магия в этой строке

(&result).type_detect(&activity);

Если result является результатом, это найдет реализацию TypeDetector на Result. В противном случае компилятор попытается вызвать type_detect на &&result, который будет использовать другую реализацию TypeDetector.

Вот полный пример (игровая площадка):

struct Activity {
    name: String,
    start_time: std::time::Instant,
}

impl Activity {
    fn begin(name: &str) -> Self {
        println!("Activity started: {}", name);
        Activity {
            name: name.to_string(),
            start_time: std::time::Instant::now(),
        }
    }

    fn end_ok(&self) {
        let duration = self.start_time.elapsed().as_millis();
        println!("Activity ended: {}, Status: Success, Duration: {}", self.name, duration);
    }

    fn end_error<T: ToString>(&self, error: &T) {
        let duration = self.start_time.elapsed().as_millis();
        println!(
            "Activity ended: {}, Status: Fail, Duration: {}, Message: {}",
            self.name,
            duration,
            error.to_string()
        );
    }
}

type MyResult = Result<(), String>;

trait TypeDetector {
    fn type_detect(&self, activity: &Activity);
}

impl<A, B: ToString> TypeDetector for Result<A, B> {
    fn type_detect(&self, activity: &Activity) {
        match self {
            Ok(_) => {
                activity.end_ok();
            }
            Err(error) => {
                activity.end_error(error);
            }
        }
    }
}

impl<A> TypeDetector for &A {
    fn type_detect(&self, activity: &Activity) {
        activity.end_ok();
    }
}

macro_rules! activity_logger {
    ($log_name:expr, $vis:vis fn $name:ident() -> $out:ty {$($body:tt)*}) => {
        $vis fn $name() -> $out {
            let activity = Activity::begin($log_name);
            let result = (||{$($body)*})();
            (&result).type_detect(&activity);
            result
        }
    }
}

activity_logger! { "TestActivity",
    fn test1() -> Result<(), Box<dyn std::error::Error>> {
        test2()?;
        Ok(())
    }
}

activity_logger! {"test2",
    fn test2() -> MyResult {
        Err("oops".to_string())
    }
}

activity_logger! {"test3",
    fn test3() -> u8 {
        3
    }
}

fn main() {
    test1();
    test3();
}

Хороший ход! Теперь все работает отлично. Большое спасибо! Я не знал, что для ссылочных типов можно реализовать черты, чтобы устранить неоднозначность.

evgenxpp 06.09.2024 02:55

Другие вопросы по теме