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
*/