Implementing Higher-Ranked Traits in Rust
Rust's type system allows for powerful abstractions, but sometimes you need to express relationships between types that go beyond simple trait bounds. Higher-ranked traits (HRTs) enable you to specify trait bounds that depend on other types, allowing for more flexible and expressive generic code. This challenge will guide you through implementing a simplified version of HRTs to understand their core concepts.
Problem Description
The goal is to create a system where a trait Lookup can be implemented for a type M that takes a type K (the key type) and a type V (the value type) as parameters. The Lookup trait will require that K implements Eq and V implements Clone. This allows M to act as a lookup table, where you can provide the key type and value type at runtime. You'll need to use associated types and a generic parameter to achieve this.
Key Requirements:
- Define a trait
Lookupwith associated typesKeyandValue. - Require that
KeyimplementsEqandValueimplementsClone. - Implement
Lookupfor a simpleVec<(Key, Value)>type. - Provide a function
lookupthat takes aLookupand aKeyand returns anOption<Value>.
Expected Behavior:
The lookup function should search the lookup table for the given key. If the key is found, it should return Some(Value) where Value is a clone of the value associated with the key. If the key is not found, it should return None.
Edge Cases to Consider:
- Empty lookup table: The function should return
Noneif the lookup table is empty. - Duplicate keys: The function should return the value associated with the first occurrence of the key.
Examples
Example 1:
Input:
let lookup_table: Vec<(i32, String)> = vec![(1, "one".to_string()), (2, "two".to_string())];
let lookup: VecLookup<i32, String> = lookup_table.into();
let result = lookup.lookup(1);
Output: Some("one".to_string())
Explanation: The key 1 is found in the lookup table, so the associated value "one" is returned as a clone.
Example 2:
Input:
let lookup_table: Vec<(i32, String)> = vec![(1, "one".to_string()), (2, "two".to_string())];
let lookup: VecLookup<i32, String> = lookup_table.into();
let result = lookup.lookup(3);
Output: None
Explanation: The key 3 is not found in the lookup table, so None is returned.
Example 3: (Edge Case - Empty Table)
Input:
let lookup_table: Vec<(i32, String)> = vec![];
let lookup: VecLookup<i32, String> = lookup_table.into();
let result = lookup.lookup(1);
Output: None
Explanation: The lookup table is empty, so None is returned.
Constraints
- The
Keytype must implementEq. - The
Valuetype must implementClone. - The
lookupfunction must returnOption<Value>. - The implementation should be reasonably efficient (linear search is acceptable for this simplified example).
- The code should compile without warnings.
Notes
- Think about how to use associated types to represent the key and value types within the
Lookuptrait. - Consider how to make the
Lookuptrait generic over the key and value types. - The
VecLookupimplementation should be straightforward, using a linear search. - The
into()method onVec<(K, V)>should convert it into aVecLookup<K, V>. This is a convenience function to simplify testing.
trait Lookup { type Key; type Value;
fn lookup(&self, key: Self::Key) -> Option<Self::Value>;
}
struct VecLookup<K, V> { data: Vec<(K, V)>, }
impl<K: Eq, V: Clone> Lookup for VecLookup<K, V> { type Key = K; type Value = V;
fn lookup(&self, key: Self::Key) -> Option<Self::Value> {
for (k, v) in &self.data {
if *k == key {
return Some(v.clone());
}
}
None
}
}
impl<K: Eq, V: Clone> From<Vec<(K, V)>> for VecLookup<K, V> { fn from(vec: Vec<(K, V)>) -> Self { VecLookup { data: vec } } }