Propagating higher-kinded-lifetimes? implementation of `Predicate` not general enough - rust

I'm trying to write a Rust library that lets users define and compose predicates for certain kinds of data. This is all easy when the predicates act on owned data, but it's turning out to be harder when they act on borrowed data. I think it's easier to understand my problem by reading an annotated code example (I'm sorry I couldn't make it shorter, but I think everything in it is required to understand my situation).
// First, some of the types that I'm interested in writing predicates for. MyValue and
// MyParsedBorrowedValue
struct MyValue {
a: String
}
struct MyParsedBorrowedValue<'a> {
_name: &'a str,
_last_name: &'a str
}
fn parse_value(value: &MyValue) -> MyParsedBorrowedValue<'_> {
// (imagine some parsing that borrows from MyValue)
MyParsedBorrowedValue {
_name: &value.a,
_last_name: &value.a
}
}
// Now, imagine I have this trait
trait Predicate<T> {
fn passes(&self, t: &T) -> bool;
}
// I can write a generic combinator like:
struct Not<T, P: Predicate<T>> {
p: P,
_p: std::marker::PhantomData<T>
}
impl<T, P: Predicate<T>> Not<T, P> {
fn new(p: P) -> Not<T, P> { Not { p, _p: std::marker::PhantomData }}
}
impl<T, P: Predicate<T>> Predicate<T> for Not<T, P> {
fn passes(&self, t: &T) -> bool {
!self.p.passes(t)
}
}
// I can write predicates on MyValue. For example:
struct IsMyValueGood {}
impl Predicate<MyValue> for IsMyValueGood {
fn passes(&self, _t: &MyValue) -> bool {
// some processing
true
}
}
// I can also write predicates on MyBorrowedParsedValue
struct IsMyParsedBorrowedValueGood {}
impl<'a> Predicate<MyParsedBorrowedValue<'a>> for IsMyParsedBorrowedValueGood {
fn passes(&self, _t: &MyParsedBorrowedValue<'a>) -> bool {
// some processing ...
false
}
}
// Finally, and this is the most important bit: I want a function that would take a
// Predicate on a "MyParsedBorrowedValue" and transform it into a predicate for a
// MyValue. Something like:
fn map_predicates(p: impl for<'a> Predicate<MyParsedBorrowedValue<'a>>)
-> impl Predicate<MyValue> {
// I believe this is a good use of higher-kinded lifetimes, though I'm not very
// familiar with them.
struct PredicateMapper<P: for<'a> Predicate<MyParsedBorrowedValue<'a>>> {
p: P
}
impl<P: for<'a> Predicate<MyParsedBorrowedValue<'a>>> Predicate<MyValue> for PredicateMapper<P> {
fn passes(&self, value: &MyValue) -> bool {
self.p.passes(&parse_value(value))
}
}
PredicateMapper { p }
}
// This is all good but sadly the following does not compile:
fn compose_predicates() {
// This compiles:
let complex_predicate_1 = map_predicates(IsMyParsedBorrowedValueGood{});
// This doesn't compile!
// Error: implementation of `Predicate` is not general enough
let complex_predicate_2 = map_predicates(Not::new(
IsMyParsedBorrowedValueGood {}
));
}
Link to compiler explorer https://godbolt.org/z/oonfcMdK3
I understand that the Not combinator is not propagating the for <'a> outside, which is what map_predicate needs to do its job. However, I don't know how I would get Not to do that, given that its definition doesn't involve any lifetimes. I guess I could make a custom NotMyParsedBorrowedValue that handles the lifetimes in the correct way, but I would prefer having a single Not combinator that works for all kinds of predicates. Is this possible?

Related

Extending On An SO Answered Question About Strategy Pattern Implementation

As answered by the ever so helpful and ubiquitous Shepmaster, could someone help me with a syntax hurdle I'm encountering?
In the previous answer, Strategy::execute() and Context::do_things() returns ().
How does one implement if a generic type is returned? Or am I missing some fundamental perspective in Rust?
I tried the following code but am currently stuck at:
struct Context<S> {
strategy: S,
}
impl<S> Context<S>
where
S: Strategy,
{
fn do_things(&self) -> T {
println!("Common preamble");
self.strategy.execute()
}
}
trait Strategy<T> {
fn execute(&self) -> T;
}
struct ConcreteStrategyA;
impl Strategy<AStruct> for ConcreteStrategyA {
fn execute(&self) -> AStruct {
println!("ConcreteStrategyA");
AStruct::default()
}
}
struct AStruct {
id: u32,
}
impl Default for AStruct {
...
}
struct ConcreteStrategyB;
impl Strategy<BStruct> for ConcreteStrategyB {
fn execute(&self) -> BStruct {
println!("ConcreteStrategyB");
BStruct::default()
}
}
struct BStruct {
id: u32,
}
impl Default for BStruct {
...
}
I have no idea where to put T for Context::do_things() -> T.
I looked around but some other samples return () as well.
Online tinkering
Thanks for reading.
Depending on what you're trying to do, it might be better to use an associated type instead of a generic. When choosing between associated types and generic parameters, you should use a generic if the caller can choose the type to use. You should use an associated type if the implementation determines the type.
Although there are exceptions (e.g. Into::into), most of the time if a type appears only in the return of a method, then this is a good indication that it should probably be an associated type. OTOH if a type is used for a parameter, then there is a fair chance that it should be a generic.
In your case, using an associated type would look like this:
struct Context<S> {
strategy: S,
}
impl<S> Context<S>
where
S: Strategy,
{
fn do_things(&self) -> S::Output {
println!("Common preamble");
self.strategy.execute()
}
}
trait Strategy {
type Output;
fn execute(&self) -> Self::Output;
}
struct ConcreteStrategyA;
impl Strategy for ConcreteStrategyA {
type Output = AStruct;
fn execute(&self) -> AStruct {
println!("ConcreteStrategyA");
AStruct::default()
}
}
#[derive (Default)]
struct AStruct {
id: u32,
}
struct ConcreteStrategyB;
impl Strategy for ConcreteStrategyB {
type Output = BStruct;
fn execute(&self) -> BStruct {
println!("ConcreteStrategyB");
BStruct::default()
}
}
#[derive (Default)]
struct BStruct {
id: u32,
}
Playground
I think using Strategy<T> more often is the key:
impl<S, T> Context<S, T>
where
S: Strategy<T>,
{
fn do_things(&self) -> T {
println!("Common preamble");
self.strategy.execute()
}
}
See here: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=a3a14895e22ca57f5f96c855b7046257

How to use traits for function overloading in Rust? [duplicate]

I am modeling an API where method overloading would be a good fit. My naïve attempt failed:
// fn attempt_1(_x: i32) {}
// fn attempt_1(_x: f32) {}
// Error: duplicate definition of value `attempt_1`
I then added an enum and worked through to:
enum IntOrFloat {
Int(i32),
Float(f32),
}
fn attempt_2(_x: IntOrFloat) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
// Can't pass the value directly
// attempt_2(i);
// attempt_2(f);
// Error: mismatched types: expected enum `IntOrFloat`
attempt_2(IntOrFloat::Int(i));
attempt_2(IntOrFloat::Float(f));
// Ugly that the caller has to explicitly wrap the parameter
}
Doing some quick searches, I've found some references that talk about overloading, and all of them seem to end in "we aren't going to allow this, but give traits a try". So I tried:
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_3(_x: &dyn IntOrFloatTrait) {}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_3(&i);
attempt_3(&f);
// Better, but the caller still has to explicitly take the reference
}
Is this the closest I can get to method overloading? Is there a cleaner way?
Yes, there is, and you almost got it already. Traits are the way to go, but you don't need trait objects, use generics:
#[derive(Debug)]
enum IntOrFloat {
Int(i32),
Float(f32),
}
trait IntOrFloatTrait {
fn to_int_or_float(&self) -> IntOrFloat;
}
impl IntOrFloatTrait for i32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Int(*self)
}
}
impl IntOrFloatTrait for f32 {
fn to_int_or_float(&self) -> IntOrFloat {
IntOrFloat::Float(*self)
}
}
fn attempt_4<T: IntOrFloatTrait>(x: T) {
let v = x.to_int_or_float();
println!("{:?}", v);
}
fn main() {
let i: i32 = 1;
let f: f32 = 3.0;
attempt_4(i);
attempt_4(f);
}
See it working here.
Here's another way that drops the enum. It's an iteration on Vladimir's answer.
trait Tr {
fn go(&self) -> ();
}
impl Tr for i32 {
fn go(&self) {
println!("i32")
}
}
impl Tr for f32 {
fn go(&self) {
println!("f32")
}
}
fn attempt_1<T: Tr>(t: T) {
t.go()
}
fn main() {
attempt_1(1 as i32);
attempt_1(1 as f32);
}
Function Overloading is Possible!!! (well, sorta...)
This Rust Playground example has more a more detailed example, and shows usage of a struct variant, which may be better for documentation on the parameters.
For more serious flexible overloading where you want to have sets of any number of parameters of any sort of type, you can take advantage of the From<T> trait for conversion of a tuple to enum variants, and have a generic function that converts tuples passed into it to the enum type.
So code like this is possible:
fn main() {
let f = Foo { };
f.do_something(3.14); // One f32.
f.do_something((1, 2)); // Two i32's...
f.do_something(("Yay!", 42, 3.14)); // A str, i32, and f64 !!
}
First, define the different sets of parameter combinations as an enum:
// The variants should consist of unambiguous sets of types.
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
Now, the conversion code; a macro can be written to do the tedious From<T> implementations, but here's what it could produce:
impl From<(i32, i32)> for FooParam {
fn from(p: (i32, i32)) -> Self {
FooParam::Bar(p.0, p.1)
}
}
impl From<f32> for FooParam {
fn from(p: f32) -> Self {
FooParam::Baz(p)
}
}
impl From<(&'static str, i32, f64)> for FooParam {
fn from(p: (&'static str, i32, f64)) -> Self {
FooParam::Qux(p.0, p.1, p.2)
}
}
And then finally, implement the struct with generic method:
struct Foo {}
impl Foo {
fn do_something<T: Into<FooParam>>(&self, t: T) {
use FooParam::*;
let fp = t.into();
match fp {
Bar(a, b) => print!("Bar: {:?}, {:?}\n", a, b),
Baz(a) => print!("Baz: {:?}\n", a),
Qux(a, b, c) => {
print!("Qux: {:?}, {:?}, {:?}\n", a, b, c)
}
}
}
}
Note: The trait bound on T needs to be specified.
Also, the variants need to be composed of combinations of types that the compiler wouldn't find ambiguous - which is an expectation for overloaded methods in other languages as well (Java/C++).
This approach has possibilities... it would be awesome if there's a decorator available - or one were written that did the From<T> implementations automatically when applied to an enum. Something like this:
// THIS DOESN'T EXIST - so don't expect the following to work.
// This is just an example of a macro that could be written to
// help in using the above approach to function overloading.
#[derive(ParameterOverloads)]
enum FooParam {
Bar(i32, i32),
Baz(f32),
Qux(&'static str, i32, f64),
}
// If this were written, it could eliminate the tedious
// implementations of From<...>.
The Builder
Another approach that addresses the case where you have multiple optional parameters to an action or configuration is the builder pattern. The examples below deviate somewhat from the recommendations in the link. Typically, there's a separate builder class/struct which finalizes the configuration and returns the configured object when a final method is invoked.
One of the most relevant situations this can apply to is where you want a constructor that takes a variable number of optional arguments - since Rust doesn't have built-in overloading, we can't have multiple versions of ___::new(). But we can get a similar effect using a chain of methods that return self. Playground link.
fn main() {
// Create.
let mut bb = BattleBot::new("Berzerker".into());
// Configure.
bb.flame_thrower(true)
.locomotion(TractorTreads)
.power_source(Uranium);
println!("{:#?}", bb);
}
Each of the configuration methods has a signature similar to:
fn power_source(&mut self, ps: PowerSource) -> &mut Self {
self.power_source = ps;
self
}
These methods could also be written to consume self and return non-reference copies or clones of self.
This approach can also be applied to actions. For instance, we could have a Command object that can be tuned with chained methods, which then performs the command when .exec() is invoked.
Applying this same idea to an "overloaded" method that we want to take a variable number of parameters, we modify our expectations a bit and have the method take an object that can be configured with the builder pattern.
let mut params = DrawParams::new();
graphics.draw_obj(params.model_path("./planes/X15.m3d")
.skin("./skins/x15.sk")
.location(23.64, 77.43, 88.89)
.rotate_x(25.03)
.effect(MotionBlur));
Alternatively, we could decide on having a GraphicsObject struct that has several config tuning methods, then performs the drawing when .draw() is invoked.

Return object implementing multiple traits - decorator pattern

I am currently implementing decorator pattern in Rust.
In Scala we could implement method chaining via traits like:
new Scanner
with Whitespaces
with Keywords
I want to do the same in Rust. So, different traits for scanner:
pub struct Lexer();
pub trait Scan {
fn scan(&self, start: &String) -> DomainTags;
}
pub struct Keywords<T> {
decorated: T
}
impl<T: Scan> Scan for Keywords<T> {
fn scan(&self, start: &String) -> DomainTags {
...
}
}
pub struct Whitespaces<T> {
decorated: T
}
impl<T: Scan> Scan for Whitespaces<T> {
fn scan(&self, start: &String) -> DomainTags {
...
}
}
But since I want to build my lexers with method like:
pub fn build() -> ??? {
let lex = Lexer();
let lex = Keyword {
decorated: Whitespaces {
decorated: lex
}
};
lex
}
I don't know if it is possible to statically deduce the return type to something like decltype(lex). What is the common approach, towards implementing the method? What could be improved?
To clarify: I want to return decltype(lex), because I may also have multiple traits for single Lexer, like:
pub trait Load {
fn load<T : Load>(&self, keywords: &String);
}
impl Load for Lexer {
fn load<Lexer>(&self, keyword : &String) {
...
}
}
And I hope to return a decorated object with implementation of Load trait as well. Both method load and scan should be available.
A function can only return a single type of value, so the type returned by your function can't depend on runtime conditions. However that type may be a boxed trait, in which case the type of the value stored in the box may change provided it implements the appropriate trait (or traits).
From the example code you've provided, I think that Lexer should be a trait like trait Lexer: Scan + Load {} (or maybe the Scan and Load traits don't need to exist at all and the scan and load methods can be defined directly in Lexer). Then your build function should just return a boxed Lexer:
pub trait Lexer {
fn scan (&self, start: &String) -> DomainTags;
fn load (&self, keyword : &String);
}
pub struct Keywords<T> {
decorated: T
}
impl<T: Lexer> Lexer for Keywords<T> {
…
}
pub struct Whitespaces<T> {
decorated: T
}
impl<T: Lexer> Lexer for Whitespaces<T> {
…
}
pub fn build (cond: bool) -> Box<dyn Lexer> {
if cond {
Box::new (Whitespaces { … })
} else {
Box::new (Keywords { … })
}
}

Can I specialise a trait on a nested type?

I have a queue Strategy trait with implementations for Monotonic and LastTick parameterised on the type I want to insert:
struct Monotonic<T> {
items: Vec<T>,
}
struct LastTick<T> {
items: Vec<T>,
}
struct SetDelta;
trait Strategy<T> {
type T;
fn enqueue(&mut self, v: T);
fn has_pending(&self) -> bool;
}
impl<T> Strategy<T> for Monotonic<T> {
type T = Self;
fn enqueue(&mut self, v: T) {
self.items.push(v);
}
fn has_pending(&self) -> bool {
!self.items.is_empty()
}
}
impl<T> Strategy<T> for LastTick<T> {
type T = Self;
fn enqueue(&mut self, v: T) {
self.items.push(v);
}
fn has_pending(&self) -> bool {
!self.items.is_empty()
}
}
impl<T> Strategy<T> for LastTick<T> {
type T = Self;
fn enqueue(&mut self, v: T) {
self.items.push(v);
}
fn has_pending(&self) -> bool {
!self.items.is_empty()
}
}
impl<T> LastTick<T> {
fn new() -> Self {
LastTick { items: Vec::new() }
}
}
impl<T> Monotonic<T> {
fn new() -> Self {
Monotonic { items: Vec::new() }
}
}
#[test]
fn monotonic_scalar_queue() {
let mut a = Monotonic::<f64>::new();
a.enqueue(123.4);
assert!(a.has_pending());
}
#[test]
fn monotonic_list_queue() {
let mut a = Monotonic::<[f64; 3]>::new();
a.enqueue([123.4, 345.8, 324.1]);
assert!(a.has_pending());
}
#[test]
fn monotonic_tuple_queue() {
let mut a = Monotonic::<(f64, String, u32)>::new();
a.enqueue((123.4, "hello".into(), 324));
assert!(a.has_pending());
}
The above work fine. I want to keep the same interface for a HashSet where the behaviour is slightly different.
#[test]
fn monotonic_set_queue() {
let mut a = Monotonic::<HashSet<f64>>::new();
// I want to insert a f64 and implement the logic of the hashset in
// the implementation, but it expects a new HashSet
a.enqueue(123.4);
assert!(a.has_pending());
}
I tried
impl<T> Strategy<T> for Monotonic<HashSet<f64>> {
type T = Self;
fn enqueue(&mut self, v: T) {
self.items.push(v);
}
fn has_pending(&self) -> bool {
!self.items.is_empty()
}
}
and also
impl Strategy<f64> for Monotonic<f64> {
type T = HashSet<f64>;
fn enqueue(&mut self, v: f64) {
self.items.push(v);
}
fn has_pending(&self) -> bool {
!self.items.is_empty()
}
}
with different results, but no luck. Is there a way to specify this easily?
It seems like you want a different implementation of Monotonic<T>, where the collection is not a Vec - that's not possible with the way you have currently defined Monotonic. You could, instead, create another type MonotonicHashSet<T>, and use HashSet as the backing collection.
If, instead, you would like to make Monotonic accept different collection types, then you may want to also genericize it over the collection type. This, however, could get complicated quickly. In Rust, the properties we generally associate with collections are split across several traits, defined in the iter module. They're split up so that each collection type can define it's behavior granularly and correctly for whatever constraints the collection has. So, for your Monotonic and LastTick types, it's important to consider what requirements you might have, and what traits that means you will require for collections to be used with that type.
One last note: While Vec accepts any type T, a HashSet requires total equality from the Eq trait, and hashability, through the Hash trait. These different requirements are worth considering, because unlike, for example, C#, Rust does not provide default implementations of these operations for all types - you have to provide them or #[derive()] them.

Can a trait guarantee certain type properties such as a vector is non-empty?

Imagine I have functions like this:
fn min_max_difference(row: &Vec<u32>) -> u32 {
let mut min_elem: u32 = row[0];
let mut max_elem: u32 = min_elem;
for &element in row.iter().skip(1) {
if element < min_elem {
min_elem = element;
} else if element > max_elem {
max_elem = element;
}
}
result = max_elem - min_elem;
}
fn execute_row_operation(row: &Vec<u32>, operation: Fn(&Vec<u32>) -> u32) -> Option<(u32, u32)> {
let mut result = None;
if row.len() > 0 {
result = operation(row);
}
result
}
Note that the if block in execute_row_operation guarantees that the Vec<u32> I am passing to the operation function is non-empty. In general, I want "operations" to be functions which only accept non-empty rows. I would like it if I could do something like this:
fn min_max_difference<T: &Vec<u32> + NonEmpty>(row: T) -> u32 {
//snip
}
This would allow the compiler to disallow passing references to empty vectors to a function like min_max_difference which expects this.
But traits as I understand them specify what methods a type has, rather than what properties a type has. In my head, I am imagining a trait for a type T that is composed of boolean predicates with type: Fn<T> -> bool, and such a trait is "implemented" for a type if it all those predicates evaluate to true.
Can something like this be achieved?
Can a trait guarantee certain type properties
Yes, that is what they are for. In many cases, these properties are that a set of functions exist (e.g. PartialEq::eq) and that a set of behaviors are present (e.g. symmetric and transitive equality, required by PartialEq).
Traits can also have no methods, such as Eq. These only add a set of behaviors (e.g. reflexive equality). These types of traits are often referred to as marker traits.
such as a vector is non-empty?
However, you aren't asking for what you really want. You actually want a way to implement a trait for certain values of a type. This is not possible in Rust.
At best, you can introduce a newtype. This might be sufficient for your needs, but you could also implement your own marker traits for that newtype, if useful:
struct NonEmptyVec<T>(Vec<T>);
impl<T> NonEmptyVec<T> {
fn new(v: Vec<T>) -> Result<Self, Vec<T>> {
if v.is_empty() {
Err(v)
} else {
Ok(NonEmptyVec(v))
}
}
}
fn do_a_thing<T>(items: NonEmptyVec<T>) {}
fn main() {
let mut a = Vec::new();
// do_a_thing(a); // expected struct `NonEmptyVec`, found struct `std::vec::Vec`
a.push(42);
let b = NonEmptyVec::new(a).expect("nope");
do_a_thing(b);
}
T: &Vec<u32> + NonEmpty
This isn't valid because Vec is a type and NonEmpty would presumably be a trait — you can't use types as trait bounds.
Historical note:
Way back in the long ago, as I understand it, Rust actually did support what you wanted under the name typestate. See What is typestate? and Typestate Is Dead, Long Live Typestate!.
An example of emulating it:
struct MyVec<T, S>
where
S: VecState,
{
vec: Vec<T>,
state: S,
}
trait VecState {}
struct Empty;
struct NonEmpty;
impl VecState for Empty {}
impl VecState for NonEmpty {}
impl<T> MyVec<T, Empty> {
fn new() -> Self {
MyVec {
vec: Vec::new(),
state: Empty,
}
}
fn push(mut self, value: T) -> MyVec<T, NonEmpty> {
self.vec.push(value);
MyVec {
vec: self.vec,
state: NonEmpty,
}
}
}
fn do_a_thing<T>(items: MyVec<T, NonEmpty>) {}
fn main() {
let a = MyVec::new();
// do_a_thing(a); // expected struct `NonEmpty`, found struct `Empty`
let b = a.push(42);
do_a_thing(b);
}

Resources