#Trait Object implementing Traits

15 messages · Page 1 of 1 (latest)

rare pendant
#

Hey is there a way to tell the compiler that every type that implements a certain trait will have other traits also implemented, such as PartialEq, Debug or Negate? ```rs
#[derive(Debug, PartialEq)]
pub enum Value {
Integer(i64),
Float(f64),
Boolean(bool),
Object(Box<dyn DynamicallySizedData>),
Null,
}

pub trait DynamicallySizedData{}
This is the error messagers
(dyn DynamicallySizedData + 'static) doesn't implement Debug
the trait Debug is not implemented for (dyn DynamicallySizedData + 'static), which is required by &Box<(dyn DynamicallySizedData + 'static)>: Debug
the following other types implement trait Debug:
(dyn Any + 'static)
(dyn Any + Send + Sync + 'static)
(dyn Any + Send + 'static)

meager epoch
rare pendant
#

My current implementation of PartialEq is only concerned with comparing primitve data types such as Value::Integer(6) == Value::Integer(6). For Dynamically allocated data types like a String I plan to compare the boxed data types first. Meaning Value::Object(Box<String>) == Value::Object(Box<String>) and only if this is true we will follow the pointer and compare the underlying data.

#

As for T I plan to extend my types to simple data structures like Trees, double linked lists, ll, Matrices and vectors

floral hazel
#

It is simply not possible

#

For a practical example of why, consider this:

impl PartialEq for dyn DynamicallySizedData {
  fn eq(&self, other: &dyn DynamicallySizedData) -> bool {
    //uh, exactly, what do we do here
  }
}
#

You don't know what actual types either of your operands are, how do you compare them?

#

They're not enums either, so you can't just match on the possible cases

#

You can get something kinda like this by having Any be a supertrait of DynamicallySizedData, if you're ok with saying that DynamicallySizedData of different types are never equal:

#
use std::{fmt::Debug, any::Any};

trait DynamicallySizedData: Debug {
  //put whatever API this trait has in your code here
  fn eq(&self, other: &dyn DynamicallySizedData) -> bool;
  fn as_any(&self) -> &dyn Any;
}

// All implementations of these two methods will follow the same basic pattern, exemplified below.
// You need to implement `as_any` to workaround the fact trait upcasting isn't stable yet
// then you can use it to downcast `other` to `self`. This assumes that two `DynamicallySizedData` whose types are different are never equal
// If you want different logic, you _might_ be able to implement it, but that depends on what it is.
#[derive(Debug, PartialEq)]
struct Example;
impl DynamicallySizedData for Example {
    fn eq(&self, other: &dyn DynamicallySizedData) -> bool { 
        other.as_any().downcast_ref::<Self>().is_some_and(|o| self == o)
    }
    fn as_any(&self) -> &dyn Any {
        self
    }
}
impl PartialEq for dyn DynamicallySizedData {
    fn eq(&self, other: &dyn DynamicallySizedData) -> bool {
        DynamicallySizedData::eq(self, other)
    }
}
#

As for Negate, what trait is that?

rare pendant
#

First of all thank you! Negate is a custom type which provides a function to implement the negation. Meaning all types that implement Negate can call .negate()

#

Btw wouldn't it be just better to have for every new data type a enum variant in Value? This is much easier to implement, doesn't force dynamic dispatch but it also forces you to change code on several place when adding a new type

floral hazel
# rare pendant Btw wouldn't it be just better to have for every new data type a enum variant in...

It doesn't necessarily, you can do the enum-dispatch trick:

enum Object {
  Foo(...),
  Bar(...),
  Baz(...),
}
impl Object {
  fn do_a_thing(&self) {
    match self {
      Self::Foo(...) => ...
      Self::Bar(...) => ...
      Self::Baz(...) => ...
    }
  }
}
```Repeat for all methods that you'd otherwise put on the trait. If you add a new variant to the enum, you only have to change its `impl` block, and no other code anywhere. This is more or less equivalent to just writing a new trait impl, it's just arranged slightly differently in your module(s).

A fairly common pattern in this case is to use the enum to delegate to more specific structs, so you can keep the code for each object type in a different place:
```rs
enum Object {
  Foo(ObjFoo),
  Bar(ObjBar),
  Baz(ObjBaz),
}
impl Object {
  fn do_a_thing(&self) {
    match self {
      Self::Foo(o) => o.do_a_thing(),
      Self::Bar(o) => o.do_a_thing(),
      Self::Baz(o) => o.do_a_thing(),
    }
  }
}

//elsewhere, possibly in other modules
struct ObjFoo { ... }
impl ObjFoo {
  fn do_a_thing(&self) { ... }
}
//repeat for the other object types
```If you do this, the enum is reduced to more or less boilerplate. Boilerplate that can be generated by the `enum-dispatch` crate