Как преобразовать элементы, на которые ссылается срез, без выделения кучи?

Предположим, есть массив параметров, которые необходимо использовать в SQL-запросе. Каждый параметр должен быть &dyn ToSql, который уже реализован для &str.

Возникает необходимость использовать объект и как &dyn ToSql, и как &str, как в приведенном ниже примере, где необходимо реализовать Display для вывода на печать.

let params = ["a", "b"];

// this works but allocates
// let tx_params = &params
//             .iter()
//             .map(|p| p as &(dyn ToSql + Sync))
//             .collect::<Vec<_>>();

// this is ideal, doesn't allocate on the heap, but doesn't work
params.map(|p| p as &(dyn ToSql + Sync));

// this has to compile, so can't just crate `params` as [&(dyn ToSql + Sync)] initially
println!("Could not insert {}", params);

Ошибка:

   Compiling playground v0.0.1 (/playground)
error[E0277]: the trait bound `str: ToSql` is not satisfied
  --> src/main.rs:14:20
   |
14 |     params.map(|p| p as &(dyn ToSql + Sync));
   |                    ^ the trait `ToSql` is not implemented for `str`
   |
   = help: the following implementations were found:
             <&'a str as ToSql>
   = note: required for the cast to the object type `dyn ToSql + Sync`

error[E0277]: the size for values of type `str` cannot be known at compilation time
  --> src/main.rs:14:20
   |
14 |     params.map(|p| p as &(dyn ToSql + Sync));
   |                    ^ doesn't have a size known at compile-time
   |
   = help: the trait `Sized` is not implemented for `str`
   = note: required for the cast to the object type `dyn ToSql + Sync`

For more information about this error, try `rustc --explain E0277`.
error: could not compile `playground` due to 2 previous errors

Черта ToSql не реализована для str, но она есть для &str, однако здесь мы заимствовали Checked не позволит нам заимствовать p, хотя мы ничего не делаем с данными, кроме приведения их к новому типу.

Детская площадка

Я вижу, у вас уже есть более конкретный ответ, но только на будущее — для такого вопроса было бы полезно сообщить нам точный тип функции, которой вы пытаетесь передать итератор — чтобы мы знали, на самом деле требуется &dyn ToSql точно или другой тип, предоставляющий ToSql, каким-то образом подойдет. Это повлияет на возможные решения.

Kevin Reid 18.03.2022 05:26
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
0
1
53
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Я боролся с этим месяц назад, и моя общая рекомендация такова: не беспокойтесь. Фактический запрос намного тяжелее, чем распределение.

Ситуация немного запутанная, потому что вам нужен &ToSql, но ToSqlреализуется для &str, поэтому вам нужны два массива: один [&str] и один [&ToSql], элементы которого ссылаются на &strs, поэтому содержимое [&ToSql] является двойными ссылками. Я не вижу простого способа добиться этого без выделения. (let params: [&&str; 2] = params.iter().collect::<Vec<_>>().try_into().unwrap(); работает, и распределение, вероятно, будет оптимизировано. Существуют ночные или небезопасные способы, см. ответ @ChayimFriedman.)

В этом случае вы можете обойти это, изначально объявив:

let params = [&"a", &"b"];

используя итератор, а не массив:

let iter = params.iter().map(|p| p as &(dyn ToSql + Sync));
client.query_raw("select * from foo where id in ?", iter);

В моем случае я не смог сделать ничего подобного, потому что я использовал выполнение, а не запрос, и execute_raw существует только на tokio-postgres, но не на postgres. Так что остерегайтесь подобных ловушек.

Ответ принят как подходящий

Я согласен с мнением @Caesar по этому поводу, однако на самом деле вы можете сделать это без выделения кучи.

Для этого вы можете использовать <[T; N]>::each_ref() (этот метод преобразует &[T; N] в [&T; N]):

params.each_ref().map(|p| p as &(dyn ToSql + Sync));

Детская площадка.

К сожалению each_ref() нестабилен, но можно написать на стабильном Rust с небезопасным кодом:

use std::iter;
use std::mem::{self, MaybeUninit};

fn array_each_ref<T, const N: usize>(arr: &[T; N]) -> [&T; N] {
    let mut result = [MaybeUninit::uninit(); N];
    for (result_item, arr_item) in iter::zip(&mut result, arr) {
        *result_item = MaybeUninit::new(arr_item);
    }
    // SAFETY: `MaybeUninit<T>` is guaranteed to have the same layout as `T`; we
    // initialized all items above (can be replaced with `MaybeUninit::array_assume_init()`
    // once stabilized).
    unsafe { mem::transmute_copy(&result) }
}

Детская площадка.

Другие вопросы по теме