Удивительно, но я застрял в проблеме 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
вообще невозможна?
(Если возможно, я бы хотел избежать динамической диспетчеризации, поскольку это довольно интенсивно используемая часть моего кода. Но на данный момент я сделаю все возможное!)
Это не совсем то, что вам нужно, но, возможно, это достаточно хорошо и в остальном дает вам возможность прояснить ваши потребности.
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);
}
Почему бы просто не иметь метод с реализацией по умолчанию, который ничего не делает? Нет необходимости использовать два метода.
@ChayimFriedman, будет ли это работать, если тип возвращаемого значения на самом деле не ()?
Нет, но я предполагаю, что все, что вы туда поместите, не нуждается в возвращаемом типе, поскольку, если он нужен, вам все равно придется его создать.
Если вы можете ограничить параметр типа f
разработчиками T
, то в «обычных» реализациях вам нужно только оставить тело t
пустым: play.rust-lang.org/…
Вы можете использовать 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()
}
}
Это имеет большой смысл. Это предполагает динамическую отправку, верно? Я подозревал, что другого выхода нет. Как вы думаете, есть ли шанс, что компилятор оптимизирует динамическую отправку?
Для общего определенного параметра fn t(&self);
динамическая диспетчеризация отсутствует, поскольку тип признака TCode
не используется с ключевым словом dyn
, см. мономорфизм. Но обращение к объекту downcast
должно происходить во время выполнения, поскольку конкретный тип нашего объекта неизвестен во время компиляции.
Это называется специализацией и невозможно в современном Rust.