I have a struct that we can construct with the builder pattern because there are some Option
al fields.
If I use the builder functions to specify these optional fields, I don't have to specify the generic parameters.
But if I don't call these functions, I need to specify the generic parameters.
Here is an example:
use Structs::*;
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T, F: Fn(T)> Struct<T, F> {
fn new(value: T) -> Struct<T, F> {
Struct {
func: None,
value: value,
}
}
fn build(self) -> Structs<T, F> {
if let Some(func) = self.func {
Struct2(self.value, func)
}
else {
Struct1(self.value)
}
}
fn func(mut self, func: F) -> Struct<T, F> {
self.func = Some(func);
self
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| { println!("{}", n); })
.build();
//let _strct = Struct::new(42).build(); // Does not compile.
let _strct = Struct::<_, &Fn(_)>::new(42).build();
}
I would like to omit the type annotation when the optional fields are not set, like so:
let _strct = Struct::new(42).build();
It should be specified that the F
type depends on T
.
I tried specifying a default type parameter as such:
impl<T, F: Fn(T) = Box<Fn(T)>> Struct<T, F> {
but it does not solve the issue.
So how can I avoid having to specify the type parameters in the Struct::new()
call?
If it is not possible to avoid this, is there any alternatives to the builder pattern that would allow me to omit the type annotation?
Generic Methods A type parameter, also known as a type variable, is an identifier that specifies a generic type name. The type parameters can be used to declare the return type and act as placeholders for the types of the arguments passed to the generic method, which are known as actual type arguments.
Generic became part of C# with version 2.0 of the language and the CLR, or Common Language Runtime. It has introduced the concept of type parameters, which allow you to design classes and methods that defer the specification of one or more types until the class or method is declared and instantiated by client code.
If T is a class or interface type, default(T) is the null reference. If T is a struct, then default(T) is the value where all the bits are zero. This is 0 for numeric types, false for bool , and a combination of both of these rules for custom struct s.
Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create classes that work with different data types.
Generics allow creating 'type variables' which can be used to create classes, functions & type aliases that don't need to explicitly define the types that they use. Generics makes it easier to write reusable code.
In TypeScript, generics can be used to better describe the types in classes that are reusable with multiple derived types. Angular does not support instantiation of generic components, but this limitation can be worked around by creating a derived component for each derived type we want to use it with.
Following Francis Gagné's clever solution, here's a similar idea that can work on stable Rust:
struct Struct<T, F: Fn(T)> {
func: Option<F>,
value: T,
}
enum Structs<T, F: Fn(T)> {
Struct1(T),
Struct2(T, F),
}
impl<T> Struct<T, fn(T)> {
fn new(value: T) -> Struct<T, fn(T)> {
Struct {
func: None,
value: value,
}
}
}
impl<T, F: Fn(T)> Struct<T, F> {
fn func<F2: Fn(T)>(self, func: F2) -> Struct<T, F2> {
Struct {
func: Some(func),
value: self.value,
}
}
fn build(self) -> Structs<T, F> {
use Structs::*;
if let Some(func) = self.func {
Struct2(self.value, func)
} else {
Struct1(self.value)
}
}
}
fn main() {
let _strct = Struct::new(42)
.func(|n| {
println!("{}", n);
})
.build();
let _strct = Struct::new(42).build();
}
Instead of a clear Void
type, we simply say that we return a struct that would be parameterized for a function pointer. You could likewise specify a reference trait object:
impl<T> Struct<T, &'static Fn(T)> {
fn new(value: T) -> Struct<T, &'static Fn(T)> {
Answering my own question from a comment:
If you don't specify a concrete type for
T
orF
, then how much space should the Rust compiler allocate to storeStruct
?
The size of a fn()
is 8 bytes on a 64-bit machine, leading to a total of 16 bytes for the entire structure:
std::mem::size_of::<fn()>();
std::mem::size_of_val(&strct);
However, when you give it a concrete callback, the structure only takes 8 bytes! That's because the type will be monomorphized for the callback, which needs no state and can basically be inlined.
Francis Gagné's solution only requires 8 bytes in each case, as the Void
type has a size of zero!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With