Вызовите функцию, если реализована черта Rust, в противном случае ничего не делайте

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

Скажи, что у меня есть черта

trait T {
    fn t(&self);
}

Я хотел бы реализовать общую функцию f<X>(x: &X), такую, что:

  • Если x реализует T, f вызывает x.t();
  • Если x не реализует T, f ничего не делает.

Я чувствую себя необъяснимо потерянным. Конечно, две реализации f не будут работать из-за повторяющихся определений. Я подумал о добавлении вспомогательной черты H с некоторой функцией h, которая по умолчанию ничего не делает, а затем о реализации H для каждой X: T, перегрузке h для вызова x.t(). Но тогда каждый X, который не реализуется T, тоже не будет реализован H, так что это не поможет. Верно?

Я что-то пропустил? Реализация f возможна, но очень запутанна? Или есть какая-то фундаментальная причина, по которой реализация f вообще невозможна?

(Если возможно, я бы хотел избежать динамической диспетчеризации, поскольку это довольно интенсивно используемая часть моего кода. Но на данный момент я сделаю все возможное!)

Это называется специализацией и невозможно в современном Rust.

Chayim Friedman 11.04.2024 17:22
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
1
1
149
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Это не совсем то, что вам нужно, но, возможно, это достаточно хорошо и в остальном дает вам возможность прояснить ваши потребности.

trait T {
    fn for_real() -> bool;
    fn t(&self);
}

#[derive(Debug)]
struct SpecialA;

impl T for SpecialA {
    fn for_real() -> bool {
        true
    }
    fn t(&self) {
        dbg!(self);
    }
}

#[derive(Debug)]
struct OrdinaryA;

impl T for OrdinaryA {
    fn for_real() -> bool {
        false
    }
    fn t(&self) {
        dbg!(self);
    }
}

fn f<X: T>(x: &X) {
    if <X as T>::for_real() {
        x.t();
    }
}

fn main() {
    f(&SpecialA);
    f(&OrdinaryA);
}

Почему бы просто не иметь метод с реализацией по умолчанию, который ничего не делает? Нет необходимости использовать два метода.

Chayim Friedman 11.04.2024 19:13

@ChayimFriedman, будет ли это работать, если тип возвращаемого значения на самом деле не ()?

hkBst 11.04.2024 19:18

Нет, но я предполагаю, что все, что вы туда поместите, не нуждается в возвращаемом типе, поскольку, если он нужен, вам все равно придется его создать.

Chayim Friedman 11.04.2024 19:25

Если вы можете ограничить параметр типа f разработчиками T, то в «обычных» реализациях вам нужно только оставить тело t пустым: play.rust-lang.org/…

eggyal 11.04.2024 20:51
Ответ принят как подходящий

Вы можете использовать downcast и downcast_mut из Любой, чтобы проверить, принадлежит ли содержащееся значение заданному типу:

use std::any::Any;

trait T {
    fn t(&self);
    fn as_any(&self) -> &dyn Any;
}

struct TCode;

impl T for TCode {
    fn t(&self) {
        println!("hello");
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}

fn main() {
    let x = TCode {};
    if let Some(TCode {}) = x.as_any().downcast_ref::<TCode>() {
        x.t()
    }
}

Детская площадка

Это имеет большой смысл. Это предполагает динамическую отправку, верно? Я подозревал, что другого выхода нет. Как вы думаете, есть ли шанс, что компилятор оптимизирует динамическую отправку?

Matteo Monti 16.04.2024 13:54

Для общего определенного параметра fn t(&self); динамическая диспетчеризация отсутствует, поскольку тип признака TCode не используется с ключевым словом dyn, см. мономорфизм. Но обращение к объекту downcast должно происходить во время выполнения, поскольку конкретный тип нашего объекта неизвестен во время компиляции.

Kaplan 17.04.2024 13:29

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