The v[i]
operation is resolved as *v.index(i)
; the .index()
method comes from the std::ops::Index
trait.
This std::ops::Index
trait cannot be directly reused in your specific case in order to return a value, since it would change its semantics.
If we don't absolutely need the []
notation, then we can define our own Indexable
trait dedicated to return a value at a given index.
All the types that already implement std::ops::Index
can be made to automatically implement this new Indexable
trait by cloning the referenced element in order to provide a value (not a reference).
Of course, this only applies to containers which elements implement Clone
.
Any specific type relevant for your use case could then implement the Indexable
in its own manner.
All of these different implementations should be usable in a common context, giving the ability to substitute an indexable with another one.
Please find below an example for all of this.
/// A specific trait to obtain a _value_ at a given index.
trait Indexable<Idx>
where
Idx: ?Sized,
{
type Output: ?Sized;
fn value_at(
&self,
idx: Idx,
) -> Self::Output;
}
/// Generic implementation of Indexable for anything that implements Index.
///
/// The stored values must be clone-able in order to provide a value
/// without consuming the container.
impl<T: ?Sized, Idx, V> Indexable<Idx> for T
where
T: std::ops::Index<Idx, Output = V>,
V: Clone,
{
type Output = V;
fn value_at(
&self,
idx: Idx,
) -> Self::Output {
self.index(idx).clone()
}
}
/// A specific type for the purpose of the example
struct Dummy {}
/// This implementation of Indexable for this specific type
/// produces a value instead of accessing a previously stored one.
impl Indexable<usize> for Dummy {
type Output = f64;
fn value_at(
&self,
idx: usize,
) -> Self::Output {
idx as f64 * 0.1
}
}
fn work_with_indexable<Src: Indexable<usize, Output = f64>>(
source: &Src,
range: std::ops::Range<usize>,
) -> f64 {
let mut accum = 0.0;
for i in range {
let v = source.value_at(i);
println!("got {} at {}", v, i);
accum += v;
}
accum
}
fn main() {
println!("~~~~ generic implementation used on a vector ~~~~");
let v = vec!["aa".to_owned(), "bb".to_owned(), "cc".to_owned()];
for i in 0..v.len() {
println!("vector at {} ~~> {}", i, v.value_at(i));
}
println!("~~~~ generic implementation used on an array ~~~~");
let a = ["dd".to_owned(), "ee".to_owned(), "ff".to_owned()];
for i in 0..a.len() {
println!("array at {} ~~> {}", i, a.value_at(i));
}
println!("~~~~ specific implementation used on a dedicated type ~~~~");
let d = Dummy {};
for i in 0..3 {
println!("dummy at {} ~~> {}", i, d.value_at(i));
}
println!("~~~~ using different implementations ~~~~");
let r1 = work_with_indexable(&[1.2, 2.3, 3.4], 0..3);
println!("slice: {}", r1);
let r2 = work_with_indexable(&d, 0..3);
println!("dummy: {}", r2);
}
/*
~~~~ generic implementation used on a vector ~~~~
vector at 0 ~~> aa
vector at 1 ~~> bb
vector at 2 ~~> cc
~~~~ generic implementation used on an array ~~~~
array at 0 ~~> dd
array at 1 ~~> ee
array at 2 ~~> ff
~~~~ specific implementation used on a dedicated type ~~~~
dummy at 0 ~~> 0
dummy at 1 ~~> 0.1
dummy at 2 ~~> 0.2
~~~~ using different implementations ~~~~
got 1.2 at 0
got 2.3 at 1
got 3.4 at 2
slice: 6.9
got 0 at 0
got 0.1 at 1
got 0.2 at 2
dummy: 0.30000000000000004
*/