How to Execute Different Implementation of a Method of a Generic Struct Based on the Generic Type

How to define different implementations for an associated function to a struct that depends on a generic type paramter in Rust?

Well, I suppose I should have thought about this longer, but the following accomplishes what I'd like without setters and getters:

// Trait locked to a type
trait Float {
fn bar(&self) -> String;
}
impl Float for f32 {
fn bar(&self) -> String {
"f32".to_string()
}
}
impl Float for f64 {
fn bar(&self) -> String {
"f64".to_string()
}
}
trait Int {
fn baz(&self) -> &'static str;
}
impl Int for i32 {
fn baz(&self) -> &'static str {
"i32"
}
}
impl Int for i64 {
fn baz(&self) -> &'static str {
"i64"
}
}

// Define all of the different implementations that we care to implement in Foo
enum MyTypes {
Float(Box <dyn Float>),
Int(Box <dyn Int>),
}

// Some kind of struct
struct Foo {
t : MyTypes,
}

// Implementation for f64
impl Foo {
fn printme(&self) {
match self.t {
MyTypes::Float(ref t) => {
println!("In a Float({})", t.bar());
}
MyTypes::Int(ref t) => {
println!("In an Int({})", t.baz());
}
}
}
}

// Takes a foo
fn foo(x : Foo) {
x.printme();
}

fn main() {
// Try both cases
foo(Foo { t : MyTypes::Float(Box::new(1.2_f32))});
foo(Foo { t : MyTypes::Int(Box::new(12_i64))});
}

Essentially, if we know that user defined data will come from a finite number of traits, we can wrap everything in an enum and then have the struct change its implementation depending on the kind of data that the user provides. This requires boxing the incoming user data based on the trait, which hides the underlying type appropriately.

How to implement methods differently depending on type in a generic class in C#?

For such a usage I would narrow T to ensure the type can be displayed. So any implementation is forced to provide the ability to display itself (w/o overriding ToString())

  public abstract class MatrixItemBase
{
public abstract string Display();
}

public class Matrix<T> where T : MatrixItemBase
{
public void Display()
{
StringBuilder sb = new StringBuilder();
try //surrounded by try-catch in case a type conversion error occurs
{
for (int i = 0; i < Rows; i++)
{
sb.AppendLine();
for (int j = 0; j < Cols; j++)
{
sb.AppendLine(_elements[i, j].Display());
}
}
Console.WriteLine(sb.ToString());
}
catch
{
Console.WriteLine("An error occurred.");
}
}
}

class Person : MatrixItemBase
{
public string Name;
public int Age;

public Person(string name, int age)
{
Name = name;
Age = age;
}

public override string Display()
{
return Name; //or whatever
}
}

or you could use an Interface instead of an abstract base class...

if you want to support structs and basic types also w/o control over the output you could remove the narrowing and add a condition to your Display Method, like

//inside your for loop
var e = _elements[i, j];
if (e is MatrixItemBase item)
sb.AppendLine(item.Display());
else
sb.AppendLine(e.ToString());

Generic implementation depending on traits

There is an unstable feature known as specialization which permits multiple impl
blocks to apply to the same type as long as one of the blocks is more specific than the other:

#![feature(specialization)]

struct Printer<T>(T);

trait Print {
fn print(&self);
}

// specialized implementation
impl<T: fmt::Display> Print for Printer<T> {
fn print(&self) {
println!("{}", self.0);
}
}

// default implementation
impl<T> Print for Printer<T> {
default fn print(&self) {
println!("I cannot be printed");
}
}

struct NotDisplay;

fn main() {
let not_printable = Printer(NotDisplay);
let printable = Printer("Hello World");

not_printable.print();
printable.print();
}

// => I cannot be printed
// => Hello World

On stable Rust, we'll need some other mechanism to accomplish specialization. Rust has another language feature capable of doing this: method resolution autoref. The compiler's rule is that if a method can be dispatched without autoref then it will be. Only if a method cannot be dispatched without autoref, the compiler will insert an autoref and attempt to resolve it again. So in this example:

impl Print for Value {
fn print(self) {
println!("Called on Value");
}
}

impl Print for &Value {
fn print(self) {
println!("Called on &Value");
}
}

The implementation for Value will be prioritized over &Value. Knowing this rule, we can mimic specialization in stable Rust:

struct Printer<T>(T);

trait Print {
fn print(&self);
}

// specialized implementation
impl<T: fmt::Display> Print for Printer<T> {
fn print(&self) {
println!("{}", self.0);
}
}

trait DefaultPrint {
fn print(&self);
}

// default implementation
//
// Note that the Self type of this impl is &Printer<T> and so the
// method argument is actually &&T!
// That makes this impl lower priority during method
// resolution than the implementation for `Print` above.
impl<T> DefaultPrint for &Printer<T> {
fn print(&self) {
println!("I cannot be printed");
}
}

struct NotDisplay;

fn main() {
let not_printable = Printer(NotDisplay);
let printable = Printer("Hello World");

(¬_printable).print();
(&printable).print();
}

// => I cannot be printed
// => Hello World

The compiler will try to use the Print implementation first. If it can't (because the type is not Display), it will then use the more general implementation of DefaultPrint.

The way that this technique applies method resolution cannot be described by a trait bound, so it will not work for regular methods, as we would have to choose between one of the traits (DefaultPrint or Print):

fn print<T: ???>(value: T) {
(&value).print()
}

However, this trick can be very useful to macros, which do not need to spell out trait bounds:

macro_rules! print {
($e:expr) => {
(&$e).print()
};
}

print!(not_printable); // => I cannot be printed
print!(printable); // => Hello World

How do I provide an implementation of a generic struct in Rust?

The type parameter <T: SomeTrait> should come right after the impl keyword:

impl<T: SomeTrait> MyStruct<T> {
fn new(t: T) -> Self {
MyStruct { value: t }
}
}

If the list of types and constraints in impl<...> becomes too long, you can use the where-syntax and list the constraints separately:

impl<T> MyStruct<T>
where
T: SomeTrait,
{
fn new(t: T) -> Self {
MyStruct { value: t }
}
}

Note the usage of Self, which is a shortcut for MyStruct<T> available inside of the impl block.


Remarks

  1. The reason why impl<T> is required is explained in this answer. Essentially, it boils down to the fact that both impl<T> MyStruct<T> and impl MyStruct<T> are valid, but mean different things.

  2. When you move new into the impl block, you should remove the superfluous type parameters, otherwise the interface of your struct will become unusable, as the following example shows:

    // trait SomeTrait and struct MyStruct as above
    // [...]

    impl<T> MyStruct<T>
    where
    T: SomeTrait,
    {
    fn new<S: SomeTrait>(t: S) -> MyStruct<S> {
    MyStruct { value: t }
    }
    }

    impl SomeTrait for u64 {}
    impl SomeTrait for u128 {}

    fn main() {
    // just a demo of problematic code, don't do this!
    let a: MyStruct<u128> = MyStruct::<u64>::new::<u128>(1234);
    // ^
    // |
    // This is an irrelevant type
    // that cannot be inferred. Not only will the compiler
    // force you to provide an irrelevant type, it will also
    // not prevent you from passing incoherent junk as type
    // argument, as this example demonstrates. This happens
    // because `S` and `T` are completely unrelated.
    }

Method on struct with generic variable

You just have to add [T] to the end of GenericCacheWrapper or [_] if you want to make it clear that you are not actually using T in the functions.

package main

import (
"encoding/json"
"fmt"
)

type GenericCacheWrapper[T any] struct {
Container T
}

func (c GenericCacheWrapper[T]) MarshalBinary() (data []byte, err error) {
return json.Marshal(c.Container)
}

func (c GenericCacheWrapper[T]) UnmarshalBinary(data []byte) error {
return json.Unmarshal(data, &c.Container)
}

func main() {
wrapper := GenericCacheWrapper[int]{Container: 4}
data, err := wrapper.MarshalBinary()
if err != nil {
panic(err)
}

fmt.Println(data)
}

This rule is defined in the language spec:

A generic type may also have methods associated with it. In this case, the method receivers must declare the same number of type parameters as present in the generic type definition.

But the reason behind this isn't very clear, perhaps to make implementation of the compiler/type checking easier.

Related: Go error: cannot use generic type without instantiation

Cast a struct with a generic type parameter to a specific type

You can still use the Any approach as shown in the linked question if you are able to constrain T: 'static

use std::any::Any;

struct A {}
struct B {}

fn f<T: 'static>(param: Vec<T>) {
if let Some(_param_a) = (¶m as &dyn Any).downcast_ref::<Vec<A>>() {
println!("I am a Vec<A>");
}
else {
println!("I am something else");
}
}

fn main() {
f(Vec::<A>::new());
f(Vec::<B>::new());
}

This does not have any runtime cost.

How to set a variable to implementations of generic typed Trait in rust?

Since the types are different, one option would be to wrap them in an enum and have some method/s for computing whatever needed depending on the decision. The enum wrapper would abstract the services operations.

struct DataRetrieverForA {}
struct DataRetrieverForB {}

struct ADataStruct {}
struct BDataStruct {}
struct MyDataStruct {}

struct ServiceA {
clientA: DataRetrieverForA,
}

struct ServiceB {
clientB: DataRetrieverForB,
}

impl ServiceA {
fn get_values(&self, id: u64) -> Result<ADataStruct, ()> {
Ok(ADataStruct {})
}
fn convert_data(
&self,
input: Result<ADataStruct, ()>,
output: &mut Vec<MyDataStruct>,
) -> Result<String, ()> {
Ok("foo".into())
}
}

impl ServiceB {
fn get_values(&self, id: u64) -> BDataStruct {
BDataStruct {}
}
fn convert_data(
&self,
input: BDataStruct,
output: &mut Vec<MyDataStruct>,
) -> Result<String, ()> {
Ok("bar".into())
}
}

enum Services {
A(ServiceA),
B(ServiceB),
}

impl Services {
fn a() -> Self {
Self::A(ServiceA {
clientA: DataRetrieverForA {},
})
}

fn b() -> Self {
Self::B(ServiceB {
clientB: DataRetrieverForB {},
})
}

fn compute(self) {
todo!()
}
}

fn main() {
let arg = "a";

let service = match arg {
"a" => Services::a(),
_ => Services::b(),
};
service.compute();
}

Playground

How can I implement a function differently depending on if a generic type implements a trait or not?

Here is a solution based on the nightly feature specialization:

#![feature(specialization)]

use std::fmt::Debug;

struct A(i32);

#[derive(Debug)]
struct B(i32);

struct Foo<T> {
data: T,
/* more fields */
}

trait Do {
fn do_something(&self);
}

impl<T> Do for Foo<T> {
default fn do_something(&self) {
/* ... */
println!("Success!");
}
}

impl<T> Do for Foo<T>
where
T: Debug,
{
fn do_something(&self) {
/* ... */
println!("Success on {:?}", self.data);
}
}

fn main() {
let foo = Foo {
data: A(3), /* ... */
};
foo.do_something(); // should call first implementation, because A
// doesn't implement Debug

let foo = Foo {
data: B(2), /* ... */
};
foo.do_something(); // should call second implementation, because B
// does implement Debug
}

The first step is to create a trait which defines do_something(&self). Now, we define two impls of this trait for Foo<T>: a generic "parent" impl which is implemented for all T and a specialized "child" impl which is only implemented for the subset where T implements Debug. The child impl may specialize items from the parent impl. These items we want to specialize need to be marked with the default keyword in the parent impl. In your example, we want to specialize do_something.



Related Topics



Leave a reply



Submit