Abstract what, how and from where one wants to get a value using 2 traits!
Forget about get_ref
, get_mut
, into_inner
and others, one get
to rule them all!
Under the hood, the abstract_getters_derive
's macro generates 3 implementations of the same trait: for T, &T, and &mut T.
It also creates a module with the StructName converted into snake_case. The module contains generated ZST structs with the same
name as the struct's fields. You can later use a qualified path like struct_name::foo
to get the field. Whether the field is returned as a reference, mutable reference, or an owned value depends on the provided value to the getter; types do not need to be adjusted manually!
In the example below you can see how the type is determined by the ownership that the get
method is given.
use abstract_getters::Get;
use abstract_getters_derive::Getters;
#[derive(Getters)]
struct ConcreteStruct {
a: i32,
b: String,
c: (usize, &'static str),
}
fn main() {
let mut concrete = ConcreteStruct {
a: 24,
b: "Hello".to_string(),
c: (7, "World"),
};
{
let a/* : &i32 */ = (&concrete).get::<concrete_struct::a>();
let b/* : &String */ = (&concrete).get::<concrete_struct::b>();
let c/* : &(usize, &str) */ = (&concrete).get::<concrete_struct::c>();
}
{
let a/* : &mut i32 */ = (&mut concrete).get::<concrete_struct::a>();
let b/* : &mut String */ = (&mut concrete).get::<concrete_struct::b>();
let c/* : &mut (usize, &str) */ = (&mut concrete).get::<concrete_struct::c>();
}
{
let a/* : i32 */ = concrete.get::<concrete_struct::a>();
// The value was moved
// let b/* : String */ = concrete.clone().get::<concrete_struct::b>();
// let c/* : (usize, &str) */ = concrete.clone().get::<concrete_struct::c>();
}
}
It's possible to require one or more fields from a value. It can be useful when designing an interface, which accepts a large struct with a lot of fields, when in reality it would only need a couple. Using these getters, you can design refactor-proof APIs! It's also possible to give users opportunities to define their own types instead of requiring them to implement yet another trait that you would need to maintain. It's always easier to define a module/struct alias rather than have duplicated fields!
use abstract_getters::{Field, Get};
use abstract_getters_derive::Getters;
#[derive(Getters, Default)]
struct LargeStruct {
// The function need only a
a: i32,
// and b
b: String,
// To be refactored
c: f64,
d: bool,
// 100500 more fields
}
fn main() {
let v = LargeStruct::default();
require_i32_and_string::<large_struct::a, large_struct::b, _>(&v);
}
/// For a `V`alue, that has a Namei32 [Field] with a [type](Field::Type) [i32]...
/// For a `V`alue, that has a NameString [Field] with a [type](Field::Type) [String]...
fn require_i32_and_string<'v, Namei32, NameString, V>(from: &'v V)
where
&'v V: Get + Field<Namei32, Type = &'v i32> + Field<NameString, Type = &'v String>,
{
let got_i32 = from.get::<Namei32>();
assert_eq!(std::any::type_name_of_val(&got_i32), "&i32");
let got_string = from.get::<NameString>();
assert_eq!(std::any::type_name_of_val(&got_string), "&alloc::string::String");
}
It is also possible to derive getters for enums, no more as_
, try_
and into_
!
use abstract_getters::{Get, Field};
use abstract_getters_derive::Getters;
#[derive(Getters)]
enum DataEnum {
Inline {
a: i32,
b: String,
},
Tuple(i32, String),
#[allow(unused)]
Unit,
Single(i32),
}
fn main() {
let mut data = DataEnum::Tuple(42, "Hello".to_string());
let wrong_variant/* : Option<&i32> */ = (&data).get::<data_enum::inline::a>();
assert_eq!(wrong_variant, None);
let int/* : Option<&mut i32> */ = (&mut data).get::<data_enum::tuple::_0>();
assert_eq!(*int.unwrap(), 42);
}