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
The reason why
impl<T>
is required is explained in this answer. Essentially, it boils down to the fact that bothimpl<T> MyStruct<T>
andimpl MyStruct<T>
are valid, but mean different things.When you move
new
into theimpl
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 impl
s 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
Tube Physics Body Acting Like Scncylinder, But How to Make It Act Like Scntube
Secure Text .Echosbullets Not Working for Password Field
Tabview, Tabitem: Running Code on Selection or Adding an Ontapgesture
Value of Type 'Authdataresult' Has No Member 'Uid'
Create a Weak Container in Swift That Accepts a Native Swift Protocol
Persist Accessibility Permissions Between Builds in Xcode 13
Swift Generic Protocol Function Parameters
Swift: How to Invalidate a Timer If the Timer Starts from a Function
How to Filter Nsarray in Swift
Datefromstring() Returns Incorrect Date
Convert Swift Encodable Class Typed as Any to Dictionary
Switch Statement for Imported Ns_Options (Rawoptionsettype) in Swift
How to Unpack Multiple Levels of Nested JSON in Firebase Database
Avaudioplayer.Play() Works But Avaudioplayernode.Play() Fails
Updated Approach to Reauthenticate a User
Randomize Two Arrays the Same Way Swift