6 个版本
0.1.5 | 2024 年 5 月 4 日 |
---|---|
0.1.4 | 2023 年 12 月 17 日 |
0.1.3 | 2022 年 1 月 28 日 |
0.1.2 | 2020 年 12 月 19 日 |
0.1.1 | 2020 年 11 月 18 日 |
#1025 在 Rust 模式
879 每月下载
28KB
231 行
可重用引用切片
用法
将以下内容添加到您的 Cargo.toml
[dependencies]
rsor = "0.1"
相关 Crates
- vecstorage 解决了与该 crate 相同的问题。虽然它在接受的类型
T
方面更灵活,但它进行的编译时检查更少,不匹配的类型可能会导致运行时恐慌。
许可证
MIT 或 Apache-2.0
lib.rs
:
可重用引用切片。
动机
此 crate 中的 Slice
数据结构可以用于解决一个非常具体的问题
- 我们想要创建一个长度在编译时未知的引用切片(即
&[&T]
或&mut [&mut T]
)
AND
- 多次调用应能够使用不兼容的生命周期(当然,每个单独调用内的所有引用都必须具有共同的生命周期)
AND
- 分配的内存应可由多次调用重用。
同时满足所有三个要求是难点。如果我们允许其中任何一个被破坏,则可以使用内置工具轻松解决这个问题
-
如果引用的数量在编译时已知,则可以使用内置数组类型(即
[&T; N]
或[&mut T; N]
),可以(并且应该!)使用。根本不需要分配任何动态内存。 -
如果所有使用的生活周期都是兼容的,则可以使用单个
Vec<&T>
并重复使用。 -
如果我们不在每次调用时关心分配内存,每次都可以使用一个新的
Vec<&T>
,允许不同的(且不兼容的)生命周期。
以下示例展示了不兼容生命周期的问题。在此示例中,引用的数量在编译时是已知的,但让我们假装它不是。
fn print_slice(slice: &[&str]) { for s in slice { print!("<{}>", s); } println!(); }
let mut vec = Vec::<&str>::with_capacity(2);
{
let one = String::from("one");
let two = String::from("two");
vec.push(&one);
vec.push(&two);
print_slice(&vec);
vec.clear();
}
let three = String::from("three");
vec.push(&three);
print_slice(&vec);
此示例无法编译,编译器会报错
error[E0597]: `one` does not live long enough
|
8 | vec.push(&one);
| ^^^^ borrowed value does not live long enough
...
12 | }
| - `one` dropped here while still borrowed
...
15 | vec.push(&three);
| --- borrow later used here
For more information about this error, try `rustc --explain E0597`.
即使 Vec<&str>
在内层作用域结束时被清空,它仍然“记住”其先前居民的生命周期,不允许未来的引用具有不兼容的生命周期。
可以使用此crate中的 Slice
类型来解决此问题
use rsor::Slice;
let mut reusable_slice = Slice::<str>::with_capacity(2);
{
let one = String::from("one");
let two = String::from("two");
let strings = reusable_slice.fill(|mut v| {
v.push(&one);
v.push(&two);
v
});
print_slice(strings);
}
let three = String::from("three");
let strings = reusable_slice.fill(|mut v| {
v.push(&three);
v
});
print_slice(strings);
assert_eq!(reusable_slice.capacity(), 2);
此示例编译成功并生成预期的输出
<one><two>
<three>
请注意,容量并未从初始值改变,即没有分配额外的内存。
常见用例
前面的示例相当人为化,为了说明不兼容生命周期的相关问题。
以下稍微更现实的示例是使用 Slice<[T]>
创建一个(可变的)切片的切片(也称为 &mut [&mut [T]]
)从(可变的)平坦切片(也称为 &mut [T]
)
use rsor::Slice;
/// Creates a slice of slices from a single larger slice.
fn sos_from_flat_slice<'a, 'b>(
reusable_slice: &'a mut Slice<[f32]>,
flat_slice: &'b mut [f32],
subslice_length: usize,
) -> &'a mut [&'b mut [f32]] {
reusable_slice.from_iter_mut(flat_slice.chunks_mut(subslice_length))
}
在某些情况下,不需要两个单独的命名生命周期;只需尝试将它们合并为一个,看看是否仍然有效。
当然,也可以对不可变切片做同样的事情,只需移除除第一个参数以外的所有 mut
实例(但包括将 .from_iter_mut()
更改为 .from_iter()
和 .chunks_mut()
更改为 .chunks()
)。
如果提供了一个指针/长度对,它可以转换为切片,使用 [std::slice::from_raw_parts_mut()
] 或 [std::slice::from_raw_parts()
].
如果给定一个“列表的列表”(例如类似于Vec<Vec<T>>
的)对象,可以使用Slice::from_refs()
(返回&[&[T]]
)或Slice::from_muts()
(返回&mut [&mut [T]]
)来将其转换为切片的切片。
在C API中,通常会有一个“指针的指针”,其中有一个指针指向包含更多指针的连续内存块,每个指针都指向另一块内存。
要将这些嵌套指针转换为嵌套切片,我们可以使用类似以下的方法
/// Creates a slice of slices from a pointer to pointers.
///
/// # Safety
///
/// `ptr` must point to `subslices` pointers with a lifetime of at least `'a`,
/// each pointing to `subslice_length` further pointers with a lifetime of at least `'b`.
unsafe fn sos_from_nested_pointers<'a, 'b>(
reusable_slice: &'a mut Slice<[f32]>,
ptr: *const *mut f32,
subslices: usize,
subslice_length: usize,
) -> &'a mut [&'b mut [f32]] {
let slice_of_ptrs = if ptr.is_null() {
&[]
} else {
// SAFETY: Correct number and lifetimes of pointers must be guaranteed by caller.
unsafe { std::slice::from_raw_parts(ptr, subslices) }
};
reusable_slice.from_iter_mut(slice_of_ptrs.iter().map(|&ptr| {
if ptr.is_null() {
&mut []
} else {
// SAFETY: Correct number and lifetimes of pointers must be guaranteed by caller.
unsafe { std::slice::from_raw_parts_mut(ptr, subslice_length) }
}
}))
}
请注意,ptr
应该在生命周期'a
内有效,而所有其他指针都应该在生命周期'b
内有效。调用者必须确保这是实际情况。这也是为什么这个函数被标记为unsafe
的许多原因之一!
更深层次的嵌套
创建这个crate的动机是为了能够启用如上例所示的切片的切片。然而,事实证明,可以存在更深层次的嵌套,例如切片的切片的切片
use rsor::Slice;
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12];
let mut level0 = Slice::with_capacity(6);
let mut level1 = Slice::with_capacity(2);
let sosos = level1.from_iter(level0.from_iter(data.chunks(2)).chunks(3));
assert_eq!(
sosos,
[[[1, 2], [3, 4], [5, 6]], [[7, 8], [9, 10], [11, 12]]]
);
assert_eq!(level0.capacity(), 6);
assert_eq!(level1.capacity(), 2);
对于每一层嵌套,都需要一个单独的Slice::
。上面的例子使用了一个Slice<[T]>
作为最内层级别,以及一个Slice<[&[T]]>
作为最外层级别。生成的切片类型为&[&[&[T]]]
。