如何在没有堆分配的情况下转换切片引用的元素?
How do I cast the elements referred by a slice without heap allocation?
假设有一个参数数组需要在 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
is implemented为&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()
is unstable,但您可以使用带有不安全代码的稳定 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) }
}
假设有一个参数数组需要在 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
is implemented为&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()
is unstable,但您可以使用带有不安全代码的稳定 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) }
}