Мне нужна черта, которая является индексируемой. Однако его реализация может быть «стандартным» массивом (тот, который фактически содержит то, что он возвращает; например, Vec целых чисел) или нестандартным массивом, который создает значение в индексной функции (и поэтому должен возвращать значение, а не ссылку). ). Как это сделать проще всего? Я не могу использовать трейт Индекс, так как он не позволит последнее. В настоящее время похоже, что мне придется обернуть «стандартные индексы» (например, Vec) пользовательской чертой индекса, которая возвращает некоторый Self::Output (а не &Self::Output). Звучит слишком много для такой простой абстракции, которая, я ожидаю, будет довольно распространенной потребностью.
(О времени жизни; будь то значение или ссылка, я намерен использовать индексированное значение только во время жизни индексатора)



Операция v[i] разрешается как *v.index(i); метод .index() происходит от признака std::ops::Index.
Этот признак std::ops::Index нельзя напрямую повторно использовать в вашем конкретном случае для возврата значения, поскольку это изменит его семантику.
Если нам абсолютно не нужна запись [], то мы можем определить свой собственный признак Indexable, предназначенный для возврата значения по заданному индексу.
Все типы, которые уже реализуют std::ops::Index, могут автоматически реализовывать этот новый признак Indexable, клонируя элемент, на который ссылаются, чтобы предоставить значение (а не ссылку).
Конечно, это относится только к контейнерам, элементы которых реализуют Clone.
Любой конкретный тип, соответствующий вашему варианту использования, может реализовать Indexable по-своему.
Все эти различные реализации должны использоваться в общем контексте, давая возможность заменять индексируемый объект другим.
Пожалуйста, найдите ниже пример для всего этого.
/// 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
*/
Плохая идея определять Indexable таким образом и всегда клонировать. Лучше определить его как GAT.
@ChayimFriedman Извините, я не знаю, как это сделать. Возможно, вам следует опубликовать ответ, который показывает лучший способ справиться с этой ситуацией. Мне было бы интересно посмотреть, как это должно быть сделано; Спасибо.
Я попытался построить пример, но мне не удалось преодолеть ограничение времени жизни. Я думаю, что это невозможно определить как GAT.
@ prog-fh Я попробовал. Дайте мне знать, если я что-то упустил или упустил из виду!
Отработав ответ, предоставленный prog-fh, вот моя попытка написать Indexable, избегая клонирования. Это позволяет нам напрямую возвращать ссылки при реализации Index, а также писать реализации, которые возвращают значения, не являющиеся ссылками.
trait Indexable<'a, Idx>
where
Idx: ?Sized,
{
type Output<'b>: ?Sized
where
Self: 'b,
'a: 'b;
fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
where
'a: 'b;
}
impl<'a, T: ?Sized, Idx, V> Indexable<'a, Idx> for T
where
T: std::ops::Index<Idx, Output = V>,
V: 'a,
{
type Output<'b> = &'b V where Self: 'b, 'a: 'b;
fn at<'b>(&'b self, idx: Idx) -> Self::Output<'b>
where
'a: 'b,
{
self.index(idx)
}
}
struct Custom {}
impl Indexable<'static, usize> for Custom {
type Output<'a> = usize;
fn at<'a>(&'a self, idx: usize) -> Self::Output<'a>
where
'static: 'a,
{
idx * 2
}
}
fn main() {
println!("testing on vector");
let v = vec!["a".to_owned(), "b".to_owned(), "c".to_owned()];
for i in 0..v.len() {
println!("{} -> {}", i, *v.at(i));
}
println!("testing on custom");
let c = Custom {};
for i in 0..3 {
println!("{} -> {}", i, c.at(i));
}
}
Просто вопрос: когда я использую его в другом свойстве (например, при возврате «индексируемого» из метода), что мне делать со временем жизни? Есть ли способ, с помощью которого я смогу не делать саму новую черту общей?
@BipoloTheGod, ваше замечание здесь, является основной проблемой этого подхода: семантика меняется от одной реализации к другой. Если некоторые реализации возвращают значения, а другие реализации возвращают ссылки, как мы можем использовать их все в общем контексте? Алгоритм работы со значениями будет отличаться от алгоритма работы со ссылками.
@prog-fh Ну, действительно, в сборке все будет иначе, но теоретически это может быть деталь реализации, которая не представлена в самом признаке (легко представить с помощью С++)
@BipoloTheGod В C++ нет различий в синтаксисе при использовании ссылки или значения (знак * неявно присутствует в C++, но это деталь, специфичная для этого специфического языка; я не знаю другого подобного языка, многие другие используйте ссылки везде, так что даже не нужно делать различие). В C или в Rust есть разница между использованием или неиспользованием операции разыменования (*), потому что в конце концов это принципиально отличается, и даже больше в Rust, где при использовании ссылок в игру вступают соображения времени жизни.
Вам действительно нужна нотация
[]? Если нет, вы можете изобрести свой собственный трейтIndexableс помощью метода.at(position), реализовать его для любого типа, реализующего стандартный трейтIndex(используя разыменование), а затем реализовать его совершенно другим способом для типов, которые должны генерировать результат вместо доступа к нему.