How to access fields of dyn structs? - rust

Recently I've been working on learning advanced Rust. As part of that, I'm learning to use dynamic dispatch.
In my tinkering I've run across a bit of an issue. For some reason, I can't seem to access fields of structs which have been assigned to variables using Boxes and dynamic dispatch. For example,
fn main() {
let z: Box<dyn S>;
z = Box::new(A::new());
println!("{}", z.val);
}
trait S {
fn new () -> Self where Self: Sized;
}
struct A {
val: i32,
}
impl S for A {
fn new () -> A {
A {val: 1}
}
}
struct B {
val: i32
}
impl S for B {
fn new() -> B {
B {val:2}
}
}
yields the error message "error[E0609]: no field val on type Box<dyn S>"
Is there any way to access such fields, or do I need to kluge together a workaround?

It is easy to understand why this does not work if you understand what a trait object is. When a method returns a dyn Trait it does not return an instance of any struct. Instead it returns a lookup table, which tells the caller where to find its methods. So the caller can access methods without knowing anything about underlying struct itself.
So if the caller does not have access to the struct itself it's clear it cannot access its fields.
There are two ways to implement what you are trying to do:
Use methods of the trait to access required fields. This is a good option if the list of possible implementors of the trait is not predetermined, and if the trait itself is not too complex (as traits that can be represented as trait objects have some limitations). Not though that trait objects come with some runtime overhead (using lookup tables is slower then direct method access).
Use enum. If you know complete list of options that the method can return, it's the best and the most Rust'y solution.
enum S {
A { val: i32, anotherAval: u32 },
B { val: i32, anotherBval: f32 },
}
impl S {
fn val(&self) -> i32 {
match self {
S::A => A.val,
S::B => B.val,
}
}
}

Related

Composition over inheritance, sure, but do we have any syntactic sugar for the function passthrough boilerplate?

If I have a struct implementing a trait, and then I encapsulate that struct in another struct, is there an easy way to pass through all the function calls to implement that trait for the second struct?
trait HasAnX {
fn get_x(&self) -> i32;
}
struct StructA {
x: i32
}
impl HasAnX for StructA {
fn get_x(&self) -> i32 {
self.x
}
}
struct StructB {
a: StructA
}
// This is the part I don't want to have to write out for every function in StructA
impl HasAnX for StructB {
fn get_x(&self) -> i32 {
self.a.get_x()
}
}
I think half of the problem is that I'm not even sure what this problem is called.
The need to implement HasAnX for StructB usually comes from looking at the problem with inheritance in mind.
To avoid this situation try to work with only the part you really care for.
If you just want to call a method on a you can do so from the outside:
struct_b.a.get_x();
If you want to work with anything like a StructA you can just implement AsRef<StructA> for it, similarly you can use AsMut for mutable access.
Then functions can just take an impl AsRef<StructA> and don't need to care what actual type it is, with the added benefit that it now also can take owned types and references.
impl AsRef<StructA> for StructB {
fn as_ref(&self) -> &StructA {
&self.a
}
}
// you can pass in any of `StructA`, `&StructA`, `StructB`, `&StructB`
pub fn reads_from_a(a: impl AsRef<StructA>) {
let a = a.as_ref();
println!("{}", a.get_x());
}
If your StructB is a sort of smart pointer you can implement Deref and just use StructAs methods directly on StructB
use std::ops::Deref;
impl Deref for StructB {
type Target = StructA;
fn deref(&self) -> &StructA {
&self.a
}
}
//...
struct_b.get_x();

How to get values in a struct-of-arrays (SOA) through a generic type?

I am looking for your feedback/advice on a piece of code.
Basically, I have a SOA like this one:
struct Entities {
pub meshes: FakeArena<Mesh>,
pub lights: FakeArena<Light>,
}
I can access a particular value through his “handle” (every handle is bound to specific type), so I could get the value of a mesh by doing entities.meshes.get(&handle).
So far, so good, but I’m trying to achieve this by dynamically retrieving the value through the corresponding arena. By doing entities.get(&handle) if the handle type is Mesh, I return entities.meshes.get(&handle). My Entities struct has a method called get:
fn get<T: Any>(&self, handle: &Handle<T>) -> &T {
let mut entity: Option<&dyn Any> = None;
let any = handle as &dyn Any;
any.downcast_ref::<Handle<Mesh>>()
.map(|handle| entity = Some(self.meshes.get(handle) as &dyn Any));
any.downcast_ref::<Handle<Light>>()
.map(|handle| entity = Some(self.lights.get(handle) as &dyn Any));
if entity.is_none() {
panic!("Type not found in stored entites.");
}
entity
.unwrap()
.downcast_ref::<T>()
.expect("Error while downcasting the entity type")
}
Playground
This works perfectly. I downcast the generic type into a concrete one, then back again into a generic one, but it seems weird and tricky.
Maybe I'm missing something, or maybe you have a better idea for this; what would you do? :)
You don't require any dynamic dispatch here, plain-old static dispatch should be enough.
Create a trait which is given a reference to your container struct. Each component type implements this trait and selects the appropriate field of the container. Then, require the trait in your get method and use it:
struct Mesh;
struct Light;
struct Entities {
meshes: Vec<Mesh>,
lights: Vec<Light>,
}
trait Example {
fn get_in<'a>(&self, entities: &'a Entities) -> &'a Self;
}
impl Example for Mesh {
fn get_in<'a>(&self, entities: &'a Entities) -> &'a Self {
&entities.meshes[0]
}
}
impl Example for Light {
fn get_in<'a>(&self, entities: &'a Entities) -> &'a Self {
&entities.lights[0]
}
}
impl Entities {
fn get<T: Example>(&self, handle: T) -> &T {
handle.get_in(self)
}
}
fn example(entities: &Entities) {
let m = entities.get(Mesh);
let l = entities.get(Light);
}

Trait method which returns `Self`-type with a different type and/or lifetime parameter

The Rust compiler doesn't allow specifying new type parameters on Self in the return types of trait methods. That is, a trait method can return Self, but not Self<T>. I'm interested in what the proper way is to accomplish the following; or if it's fundamentally impossible, then why that's the case.
Suppose we have two structs as follows:
pub struct SomeStruct<T> {
id: usize,
description: String,
extra_data: T,
}
pub struct OtherStruct<T> {
permit_needed: bool,
registration_number: usize,
extra_data: T,
}
Both of these structs have an extra_data field of type T, where T is a generic type parameter. We can implement methods such as get_extra_data(), and more interestingly, transform(), on both structs:
impl<T> SomeStruct<T> {
pub fn get_extra_data(&self) -> &T {
&self.extra_data
}
pub fn transform<S>(&self, data: S) -> SomeStruct<S> {
SomeStruct {
id: self.id,
description: self.description.clone(),
extra_data: data,
}
}
}
impl<T> OtherStruct<T> {
pub fn get_extra_data(&self) -> &T {
&self.extra_data
}
pub fn transform<S>(&self, data: S) -> OtherStruct<S> {
OtherStruct {
permit_needed: self.permit_needed,
registration_number: self.registration_number,
extra_data: data,
}
}
}
But I'm having trouble creating a trait Transformable that unifies these two types with respect to these operations:
trait Transformable<T> {
fn get_extra_data(&self) -> &T;
fn transform<S>(&self, data: S) -> Self<S>;
}
The transform() function needs to return something of the same type as Self, just with a different type parameter. Unfortunately, the rust compiler rejects this with the error message E0109: type arguments are not allowed for this type.
Even the weaker signature fn transform<S>(&self, data: S) -> impl Transformable<S> is rejected, with error E0562: impl Trait not allowed outside of function and inherent method return types. The traditional solution here, to my knowledge, would be to return an associated type instead of an impl, but that would require generic associated types in this case. And at any rate that would seem much more roundabout than the stronger Self<S> signature which is what we really want.
Playground link with the above code
An analogous problem happens exactly the same way with lifetime parameters. I'm actually more interested in the lifetime parameters case, but I thought the type parameters case might be easier to understand and talk about, and also make it clear that the issue isn't lifetime-specific.

Generic APIs with traits

I'm attempting to generalize/decouple an API, so that alternative structs that satisfy a Trait can be used. Struggling on Syntax regarding nested traits. This is a stripped down example of what I'm attempting to do. Note that in the real one, there are multiple sub-traits, which potentially makes a follow-on question of "how to reduce verbosity, if I'm on the right track".
pub mod general {
/// A trait to make the API generic
pub trait MyTrait<A: PartialEq> {
// type A: Partial
fn val(self) -> A;
}
/// Part of the generic API
pub struct Data<A: PartialEq, T: MyTrait<A>> {
mys: T
}
/// Another part of the generic API
fn doit<A: PartialEq>(s: impl MyTrait<A>) -> impl MyTrait {
s
}
}
pub mod specific {
/// Api-specific substruct
#[derive(PartialEq)]
pub struct Aval {
val: u32
}
/// My top-level custom struct
pub struct MyS {
val: Aval
}
}
impl<A: PartialEq> crate::general::MyTrait<A> for crate::specific::MyS {
fn val(self) -> crate::specific::Aval {
self.val
}
}
/// Example of how we'd use this
fn main() {
let mys = crate::specific::MyS{ val: crate::specific::Aval{ val: 0 } };
let S = crate::general::Data{mys};
crate::general::doit(mys); // Eg pretend we cloned or didn't create S.
}
Playground
In this specific example, we have a chicken+egg: Error on the Data struct of error[E0392]: parameter `A` is never used. Where if we remove A: error[E0412]: cannot find type `A` in this scope
I suspect this is a syntax problem related to associated types.
Just remove the trait bound from your struct.
struct Data<T> {
mys: T
}
You can still add methods and trait implementations for Data<T> requiring more specific bounds on T, but you don’t need them to define the layout of Data<T>, so you shouldn’t specify them there (and in this case, you can’t, unless you add a PhantomData member referring to A).
Your code has a number of other errors, but I trust you’ll figure them out. Feel free to comment if you get stuck again.

Can I cast between two traits?

Is there a way to cast from one trait to another?
I have the traits Foo and Bar and a Vec<Box<dyn Foo>>. I know some of the items in the Vec implement the Bar trait, but is there any way I could target them?
I don't understand if this is possible or not.
trait Foo {
fn do_foo(&self);
}
trait Bar {
fn do_bar(&self);
}
struct SomeFoo;
impl Foo for SomeFoo {
fn do_foo(&self) {
println!("doing foo");
}
}
struct SomeFooBar;
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
}
impl Bar for SomeFooBar {
fn do_bar(&self) {
println!("doing bar");
}
}
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
// if let Some(val) = foo.downcast_whatever::<Bar>() {
// val.bar();
// }
}
}
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=8b637bddc4fc923ce705e84ad1d783d4)
No. There is no way to cast between two unrelated traits. To understand why, we have to understand how trait objects are implemented. To start with, let's look at TraitObject.
TraitObject is a reflection of how trait objects are actually implemented. They are composed of two pointers: data and vtable. The data value is just a reference to the original object:
#![feature(raw)]
use std::{mem, raw};
trait Foo {}
impl Foo for u8 {}
fn main() {
let i = 42u8;
let t = &i as &dyn Foo;
let to: raw::TraitObject = unsafe { mem::transmute(t) };
println!("{:p}", to.data);
println!("{:p}", &i);
}
vtable points to a table of function pointers. This table contains references to each implemented trait method, ordered by some compiler-internal manner.
For this hypothetical input
trait Foo {
fn one(&self);
}
impl Foo for u8 {
fn one(&self) { println!("u8!") }
}
The table is something like this pseudocode
const FOO_U8_VTABLE: _ = [impl_of_foo_u8_one];
A trait object knows a pointer to the data and a pointer to a list of methods that make up that trait. From this information, there is no way to get any other piece of data.
Well, almost no way. As you might guess, you can add a method to the vtable that returns a different trait object. In computer science, all problems can be solved by adding another layer of indirection (except too many layers of indirection).
See also:
Why doesn't Rust support trait object upcasting?
But couldn't the data part of the TraitObject be transmuted to the struct
Not safely, no. A trait object contains no information about the original type. All it has is a raw pointer containing an address in memory. You could unsafely transmute it to a &Foo or a &u8 or a &(), but neither the compiler nor the runtime data have any idea what concrete type it originally was.
The Any trait actually does this by also tracking the type ID of the original struct. If you ask for a reference to the correct type, the trait will transmute the data pointer for you.
Is there a pattern other than the one I described with my FooOrBar trait to handle such cases where we need to iterate over a bunch of trait objects but treat some of them slightly different?
If you own these traits, then you can add as_foo to the Bar trait and vice versa.
You could create an enum that holds either a Box<dyn Foo> or a Box<dyn Bar> and then pattern match.
You could move the body of bar into the body of foo for that implementation.
You could implement a third trait Quux where calling <FooStruct as Quux>::quux calls Foo::foo and calling <BarStruct as Quux>::quux calls Bar::foo followed by Bar::bar.
so... I don't think this is exactly what you want, but it's the closest I can get.
// first indirection: trait objects
let sf: Box<Foo> = Box::new(SomeFoo);
let sb: Box<Bar> = Box::new(SomeFooBar);
// second level of indirection: Box<Any> (Any in this case
// is the first Box with the trait object, so we have a Box<Box<Foo>>
let foos: Vec<Box<Any>> = vec![Box::new(sf), Box::new(sb)];
// downcasting to the trait objects
for foo in foos {
match foo.downcast::<Box<Foo>>() {
Ok(f) => f.do_foo(),
Err(other) => {
if let Ok(bar) = other.downcast::<Box<Bar>>() {
bar.do_bar();
}
}
}
}
note that we can call SomeFooBar as a Box<Bar> only because we stored it as a Box<Bar> in the first place. So this is still not what you want (SomeFooBar is a Foo too, but you can't convert it to a Box<Foo> any longer, so we're not really converting one trait to the other)
The short answer is: there is extremely limited support for downcasting at the moment in the language.
The long answer is that being able to downcast is not seen as high-priority for both technical and philosophical reasons:
from a technical stand-point, there are workarounds for most if not all situations
from a philosophical stand-point, downcasting leads to more brittle software (as you unexpectedly start relying on implementation details)
There have been multiple proposals, and I myself participated, but for now none has been selected and it is unclear whether Rust will ever get downcasting or if it does what its limitations will be.
In the mean time, you have essentially two workarounds:
Use TypeId: each type has an associated TypeId value which can be queried, then you can build a type-erased container such as Any and query whether the type it holds is a specific X. Behind the scenes Any will simply check the TypeId of this X against the TypeId of the value stored.
Create a specific trait, as you did.
The latter is more open-ended, and notably can be used with traits, whereas the former is limited to concrete types.
Here is what I did.
I added an as_bar method to the Foo trait that returns an Option<&Bar>. I gave the trait a default implementation to return None so that there is little to no inconvenience for Foo implementers that don't bother about Bar.
trait Foo {
fn do_foo(&self);
fn as_bar(&self) -> Option<&dyn Bar> {
None
}
}
I overwrite that method for the SomeFooBar struct which implements both Foo and Bar to return Some(self):
impl Foo for SomeFooBar {
fn do_foo(&self) {
println!("doing foo");
}
fn as_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
}
Which makes the calling code look pretty much the way I want it to look.
fn main() {
let foos: Vec<Box<dyn Foo>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.do_foo();
if let Some(bar) = foo.as_bar() {
bar.do_bar();
}
}
}
Playground
I would love to see Rust improve on this part in the future, but it's a solution I can totally live with for my case.
The only solution that I found originally is to introduce a third trait FooOrBar with explicit converter methods and implement that for both types. It doesn't feel like the right tool for the job though.
trait FooOrBar {
fn to_bar(&self) -> Option<&dyn Bar>;
fn to_foo(&self) -> Option<&dyn Foo>;
}
impl FooOrBar for SomeFooBar {
fn to_bar(&self) -> Option<&dyn Bar> {
Some(self)
}
fn to_foo(&self) -> Option<&dyn Foo> {
None
}
}
impl FooOrBar for SomeFoo {
fn to_bar(&self) -> Option<&dyn Bar> {
None
}
fn to_foo(&self) -> Option<&dyn Foo> {
Some(self)
}
}
fn main() {
let foos: Vec<Box<dyn FooOrBar>> = vec![Box::new(SomeFoo), Box::new(SomeFooBar)];
for foo in foos {
foo.to_foo().map(|foo| foo.do_foo());
foo.to_bar().map(|foo| foo.do_bar());
}
}

Resources