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 日

#1025Rust 模式

Download history 446/week @ 2024-05-03 248/week @ 2024-05-10 189/week @ 2024-05-17 202/week @ 2024-05-24 200/week @ 2024-05-31 176/week @ 2024-06-07 173/week @ 2024-06-14 198/week @ 2024-06-21 119/week @ 2024-06-28 88/week @ 2024-07-05 114/week @ 2024-07-12 128/week @ 2024-07-19 230/week @ 2024-07-26 127/week @ 2024-08-02 410/week @ 2024-08-09 91/week @ 2024-08-16

879 每月下载

MIT/Apache

28KB
231

可重用引用切片

用法

将以下内容添加到您的 Cargo.toml

[dependencies]
rsor = "0.1"
  • 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]]]

无运行时依赖