Предположим, есть массив параметров, которые необходимо использовать в SQL-запросе. Каждый параметр должен быть &dyn ToSql
, который уже реализован для &str
.
Возникает необходимость использовать объект и как &dyn ToSql
, и как &str
, как в приведенном ниже примере, где необходимо реализовать Display
для вывода на печать.
let params = ["a", "b"];
// this works but allocates
// let tx_params = ¶ms
// .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
, хотя мы ничего не делаем с данными, кроме приведения их к новому типу.
Я боролся с этим месяц назад, и моя общая рекомендация такова: не беспокойтесь. Фактический запрос намного тяжелее, чем распределение.
Ситуация немного запутанная, потому что вам нужен &ToSql
, но ToSql
реализуется для &str
, поэтому вам нужны два массива: один [&str]
и один [&ToSql]
, элементы которого ссылаются на &str
s, поэтому содержимое [&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) }
}
Я вижу, у вас уже есть более конкретный ответ, но только на будущее — для такого вопроса было бы полезно сообщить нам точный тип функции, которой вы пытаетесь передать итератор — чтобы мы знали, на самом деле требуется
&dyn ToSql
точно или другой тип, предоставляющий ToSql, каким-то образом подойдет. Это повлияет на возможные решения.