Merge branch 'rosenpass:main' into main

This commit is contained in:
James Brownlee
2024-01-02 18:27:38 -05:00
committed by GitHub
5 changed files with 249 additions and 45 deletions

View File

@@ -25,6 +25,14 @@ jobs:
- name: Run ShellCheck - name: Run ShellCheck
uses: ludeeus/action-shellcheck@master uses: ludeeus/action-shellcheck@master
rustfmt:
name: Rust Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Run Rust Formatting Script
run: bash format_rust_code.sh --mode check
cargo-audit: cargo-audit:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

115
format_rust_code.sh Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bash
# Parse command line options
while [[ $# -gt 0 ]]; do
case "$1" in
--mode)
mode="$2"
shift 2
;;
*)
echo "Unknown option: $1"
exit 1
;;
esac
done
# Check if mode is specified
if [ -z "$mode" ]; then
echo "Please specify the mode using --mode option. Valid modes are 'check' and 'fix'."
exit 1
fi
# Find all Markdown files in the current directory and its subdirectories
mapfile -t md_files < <(find . -type f -name "*.md")
count=0
# Iterate through each Markdown file
for file in "${md_files[@]}"; do
# Use awk to extract Rust code blocks enclosed within triple backticks
rust_code_blocks=$(awk '/```rust/{flag=1; next}/```/{flag=0} flag' "$file")
# Count the number of Rust code blocks
num_fences=$(awk '/```rust/{f=1} f{if(/```/){f=0; count++}} END{print count}' "$file")
if [ -n "$rust_code_blocks" ]; then
echo "Processing Rust code in $file"
# Iterate through each Rust code block
for ((i=1; i <= num_fences ; i++)); do
# Extract individual Rust code block using awk
current_rust_block=$(awk -v i="$i" '/```rust/{f=1; if (++count == i) next} f&&/```/{f=0;next} f' "$file")
# Variable to check if we have added the main function
add_main=0
# Check if the Rust code block is already inside a function
if ! echo "$current_rust_block" | grep -q "fn main()"; then
# If not, wrap it in a main function
current_rust_block=$'fn main() {\n'"$current_rust_block"$'\n}'
add_main=1
fi
if [ "$mode" == "check" ]; then
# Apply changes to the Rust code block
formatted_rust_code=$(echo "$current_rust_block" | rustfmt)
# Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function
if [ "$add_main" == 1 ]; then
formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //')
current_rust_block=$(echo "$current_rust_block" | sed '1d;')
current_rust_block=$(echo "$current_rust_block" | sed '$d')
fi
if [ "$formatted_rust_code" == "$current_rust_block" ]; then
echo "No changes needed in Rust code block $i in $file"
else
echo -e "\nChanges needed in Rust code block $i in $file:\n"
echo "$formatted_rust_code"
count=+1
fi
elif [ "$mode" == "fix" ]; then
# Replace current_rust_block with formatted_rust_code in the file
formatted_rust_code=$(echo "$current_rust_block" | rustfmt)
# Use rustfmt to format the Rust code block, remove first and last lines, and remove the first 4 spaces if added main function
if [ "$add_main" == 1 ]; then
formatted_rust_code=$(echo "$formatted_rust_code" | sed '1d;$d' | sed 's/^ //')
current_rust_block=$(echo "$current_rust_block" | sed '1d;')
current_rust_block=$(echo "$current_rust_block" | sed '$d')
fi
# Check if the formatted code is the same as the current Rust code block
if [ "$formatted_rust_code" == "$current_rust_block" ]; then
echo "No changes needed in Rust code block $i in $file"
else
echo "Formatting Rust code block $i in $file"
# Replace current_rust_block with formatted_rust_code in the file
# Use awk to find the line number of the pattern
start_line=$(grep -n "^\`\`\`rust" "$file" | sed -n "${i}p" | cut -d: -f1)
end_line=$(grep -n "^\`\`\`" "$file" | awk -F: -v start_line="$start_line" '$1 > start_line {print $1; exit;}')
if [ -n "$start_line" ] && [ -n "$end_line" ]; then
# Print lines before the Rust code block
head -n "$((start_line - 1))" "$file"
# Print the formatted Rust code block
echo "\`\`\`rust"
echo "$formatted_rust_code"
echo "\`\`\`"
# Print lines after the Rust code block
tail -n +"$((end_line + 1))" "$file"
else
# Rust code block not found or end line not found
cat "$file"
fi > tmpfile && mv tmpfile "$file"
fi
else
echo "Unknown mode: $mode. Valid modes are 'check' and 'fix'."
exit 1
fi
done
fi
done
# CI failure if changes are needed
if [ $count -gt 0 ]; then
echo "CI failed: Changes needed in Rust code blocks."
exit 1
fi

View File

@@ -87,6 +87,15 @@ pub enum Cli {
force: bool, force: bool,
}, },
/// Deprecated - use gen-keys instead
#[allow(rustdoc::broken_intra_doc_links)]
#[allow(rustdoc::invalid_html_tags)]
Keygen {
// NOTE yes, the legacy keygen argument initially really accepted "privet-key", not "secret-key"!
/// public-key <PATH> private-key <PATH>
args: Vec<String>,
},
/// Validate a configuration /// Validate a configuration
Validate { config_files: Vec<PathBuf> }, Validate { config_files: Vec<PathBuf> },
@@ -119,6 +128,40 @@ impl Cli {
config::Rosenpass::example_config().store(config_file)?; config::Rosenpass::example_config().store(config_file)?;
} }
// Deprecated - use gen-keys instead
Keygen { args } => {
log::warn!("The 'keygen' command is deprecated. Please use the 'gen-keys' command instead.");
let mut public_key: Option<PathBuf> = None;
let mut secret_key: Option<PathBuf> = None;
// Manual arg parsing, since clap wants to prefix flags with "--"
let mut args = args.into_iter();
loop {
match (args.next().as_ref().map(String::as_str), args.next()) {
(Some("private-key"), Some(opt)) | (Some("secret-key"), Some(opt)) => {
secret_key = Some(opt.into());
}
(Some("public-key"), Some(opt)) => {
public_key = Some(opt.into());
}
(Some(flag), _) => {
bail!("Unknown option `{}`", flag);
}
(_, _) => break,
};
}
if secret_key.is_none() {
bail!("private-key is required");
}
if public_key.is_none() {
bail!("public-key is required");
}
generate_and_save_keypair(secret_key.unwrap(), public_key.unwrap())?;
}
GenKeys { GenKeys {
config_file, config_file,
public_key, public_key,
@@ -160,12 +203,7 @@ impl Cli {
} }
// generate the keys and store them in files // generate the keys and store them in files
let mut ssk = crate::protocol::SSk::random(); generate_and_save_keypair(skf, pkf)?;
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(skf)?;
spk.store_secret(pkf)?;
} }
ExchangeConfig { config_file } => { ExchangeConfig { config_file } => {
@@ -246,3 +284,12 @@ impl Cli {
srv.event_loop() srv.event_loop()
} }
} }
/// generate secret and public keys, store in files according to the paths passed as arguments
fn generate_and_save_keypair(secret_key: PathBuf, public_key: PathBuf) -> anyhow::Result<()> {
let mut ssk = crate::protocol::SSk::random();
let mut spk = crate::protocol::SPk::random();
StaticKem::keygen(ssk.secret_mut(), spk.secret_mut())?;
ssk.store_secret(secret_key)?;
spk.store_secret(public_key)
}

View File

@@ -5,7 +5,8 @@ use std::process::exit;
/// Catches errors, prints them through the logger, then exits /// Catches errors, prints them through the logger, then exits
pub fn main() { pub fn main() {
env_logger::init(); // default to displaying warning and error log messages only
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("warn")).init();
let res = attempt!({ let res = attempt!({
rosenpass_sodium::init()?; rosenpass_sodium::init()?;

View File

@@ -12,15 +12,17 @@ The crate provides chained functions to simplify allocating the destination para
For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may. For now this crate is experimental; patch releases are guaranteed not to contain any breaking changes, but minor releases may.
```rust ```rust
use std::ops::BitXorAssign;
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array; use rosenpass_to::ops::copy_array;
use rosenpass_to::{to, with_destination, To};
use std::ops::BitXorAssign;
// Destination functions return some value that implements the To trait. // Destination functions return some value that implements the To trait.
// Unfortunately dealing with lifetimes is a bit more finicky than it would# // Unfortunately dealing with lifetimes is a bit more finicky than it would#
// be without destination parameters // be without destination parameters
fn xor_slice<'a, T>(src: &'a[T]) -> impl To<[T], ()> + 'a fn xor_slice<'a, T>(src: &'a [T]) -> impl To<[T], ()> + 'a
where T: BitXorAssign + Clone { where
T: BitXorAssign + Clone,
{
// Custom implementations of the to trait can be created, but the easiest // Custom implementations of the to trait can be created, but the easiest
with_destination(move |dst: &mut [T]| { with_destination(move |dst: &mut [T]| {
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
@@ -65,7 +67,7 @@ assert_eq!(&dst[..], &flip01[..]);
// The builtin function copy_array supports to_value() since its // The builtin function copy_array supports to_value() since its
// destination parameter is a fixed size array, which can be allocated // destination parameter is a fixed size array, which can be allocated
// using default() // using default()
let dst : [u8; 4] = copy_array(flip01).to_value(); let dst: [u8; 4] = copy_array(flip01).to_value();
assert_eq!(&dst, flip01); assert_eq!(&dst, flip01);
``` ```
@@ -84,7 +86,9 @@ Functions declared like this are more cumbersome to use when the destination par
use std::ops::BitXorAssign; use std::ops::BitXorAssign;
fn xor_slice<T>(dst: &mut [T], src: &[T]) fn xor_slice<T>(dst: &mut [T], src: &[T])
where T: BitXorAssign + Clone { where
T: BitXorAssign + Clone,
{
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
for (d, s) in dst.iter_mut().zip(src.iter()) { for (d, s) in dst.iter_mut().zip(src.iter()) {
*d ^= s.clone(); *d ^= s.clone();
@@ -114,8 +118,8 @@ assert_eq!(&dst[..], &flip01[..]);
There are a couple of ways to use a function with destination: There are a couple of ways to use a function with destination:
```rust ```rust
use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice_least}; use rosenpass_to::ops::{copy_array, copy_slice_least};
use rosenpass_to::{to, To};
let mut dst = b" ".to_vec(); let mut dst = b" ".to_vec();
@@ -129,7 +133,8 @@ copy_slice_least(b"This is fin").to(&mut dst[..]);
assert_eq!(&dst[..], b"This is fin"); assert_eq!(&dst[..], b"This is fin");
// You can allocate the destination variable on the fly using `.to_this(...)` // You can allocate the destination variable on the fly using `.to_this(...)`
let tmp = copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned()); let tmp =
copy_slice_least(b"This is new---").to_this(|| b"This will be overwritten".to_owned());
assert_eq!(&tmp[..], b"This is new---verwritten"); assert_eq!(&tmp[..], b"This is new---verwritten");
// You can allocate the destination variable on the fly `.collect(..)` if it implements default // You can allocate the destination variable on the fly `.collect(..)` if it implements default
@@ -147,8 +152,11 @@ assert_eq!(&tmp[..], b"Fixed");
The to crate provides basic functions with destination for copying data between slices and arrays. The to crate provides basic functions with destination for copying data between slices and arrays.
```rust ```rust
use rosenpass_to::ops::{
copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice,
try_copy_slice_least_src,
};
use rosenpass_to::{to, To}; use rosenpass_to::{to, To};
use rosenpass_to::ops::{copy_array, copy_slice, copy_slice_least, copy_slice_least_src, try_copy_slice, try_copy_slice_least_src};
let mut dst = b" ".to_vec(); let mut dst = b" ".to_vec();
@@ -161,18 +169,33 @@ to(&mut dst[4..], copy_slice_least_src(b"!!!"));
assert_eq!(&dst[..], b"Hell!!!orld"); assert_eq!(&dst[..], b"Hell!!!orld");
// Copy a slice, copying as many bytes as possible // Copy a slice, copying as many bytes as possible
to(&mut dst[6..], copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx")); to(
&mut dst[6..],
copy_slice_least(b"xxxxxxxxxxxxxxxxxxxxxxxxxxxxx"),
);
assert_eq!(&dst[..], b"Hell!!xxxxx"); assert_eq!(&dst[..], b"Hell!!xxxxx");
// Copy a slice, will return None and abort if the sizes do not much // Copy a slice, will return None and abort if the sizes do not much
assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World"))); assert_eq!(Some(()), to(&mut dst[..], try_copy_slice(b"Hello World")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---------------------"))); assert_eq!(
None,
to(&mut dst[..], try_copy_slice(b"---------------------"))
);
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
// Copy a slice, will return None and abort if source is longer than destination // Copy a slice, will return None and abort if source is longer than destination
assert_eq!(Some(()), to(&mut dst[4..], try_copy_slice_least_src(b"!!!"))); assert_eq!(
assert_eq!(None, to(&mut dst[4..], try_copy_slice_least_src(b"-------------------------"))); Some(()),
to(&mut dst[4..], try_copy_slice_least_src(b"!!!"))
);
assert_eq!(
None,
to(
&mut dst[4..],
try_copy_slice_least_src(b"-------------------------")
)
);
assert_eq!(&dst[..], b"Hell!!!orld"); assert_eq!(&dst[..], b"Hell!!!orld");
// Copy fixed size arrays all at once // Copy fixed size arrays all at once
@@ -186,12 +209,14 @@ assert_eq!(&dst, b"Hello");
The easiest way to declare a function with destination is to use the with_destination function. The easiest way to declare a function with destination is to use the with_destination function.
```rust ```rust
use rosenpass_to::{To, to, with_destination};
use rosenpass_to::ops::copy_array; use rosenpass_to::ops::copy_array;
use rosenpass_to::{to, with_destination, To};
/// Copy the given slice to the start of a vector, reusing its memory if possible /// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a fn copy_to_vec<'a, T>(src: &'a [T]) -> impl To<Vec<T>, ()> + 'a
where T: Clone { where
T: Clone,
{
with_destination(move |dst: &mut Vec<T>| { with_destination(move |dst: &mut Vec<T>| {
dst.clear(); dst.clear();
dst.extend_from_slice(src); dst.extend_from_slice(src);
@@ -217,7 +242,9 @@ The same pattern can be implemented without `to`, at the cost of being slightly
```rust ```rust
/// Copy the given slice to the start of a vector, reusing its memory if possible /// Copy the given slice to the start of a vector, reusing its memory if possible
fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T]) fn copy_to_vec<T>(dst: &mut Vec<T>, src: &[T])
where T: Clone { where
T: Clone,
{
dst.clear(); dst.clear();
dst.extend_from_slice(src); dst.extend_from_slice(src);
} }
@@ -240,11 +267,11 @@ Alternative functions are returned, that return a `to::Beside` value, containing
destination variable and the return value. destination variable and the return value.
```rust ```rust
use std::cmp::{min, max}; use rosenpass_to::{to, with_destination, Beside, To};
use rosenpass_to::{To, to, with_destination, Beside}; use std::cmp::{max, min};
/// Copy an array of floats and calculate the average /// Copy an array of floats and calculate the average
pub fn copy_and_average<'a>(src: &'a[f64]) -> impl To<[f64], f64> + 'a { pub fn copy_and_average<'a>(src: &'a [f64]) -> impl To<[f64], f64> + 'a {
with_destination(move |dst: &mut [f64]| { with_destination(move |dst: &mut [f64]| {
assert!(src.len() == dst.len()); assert!(src.len() == dst.len());
let mut sum = 0f64; let mut sum = 0f64;
@@ -300,8 +327,8 @@ assert_eq!(tmp, Beside([42f64; 3], 42f64));
When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value. When Beside values contain a `()`, `Option<()>`, or `Result<(), Error>` return value, they expose a special method called `.condense()`; this method consumes the Beside value and condenses destination and return value into one value.
```rust ```rust
use rosenpass_to::Beside;
use std::result::Result; use std::result::Result;
use rosenpass_to::{Beside};
assert_eq!((), Beside((), ()).condense()); assert_eq!((), Beside((), ()).condense());
@@ -318,8 +345,8 @@ assert_eq!(Err(()), Beside(42, err_unit).condense());
When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value: When condense is implemented for a type, `.to_this(|| ...)`, `.to_value()`, and `.collect::<...>()` on the `To` trait can be used even with a return value:
```rust ```rust
use rosenpass_to::ops::try_copy_slice;
use rosenpass_to::To; use rosenpass_to::To;
use rosenpass_to::ops::try_copy_slice;;
let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>(); let tmp = try_copy_slice(b"Hello World").collect::<[u8; 11]>();
assert_eq!(tmp, Some(*b"Hello World")); assert_eq!(tmp, Some(*b"Hello World"));
@@ -337,8 +364,8 @@ assert_eq!(tmp, None);
The same naturally also works for Results, but the example is a bit harder to motivate: The same naturally also works for Results, but the example is a bit harder to motivate:
```rust ```rust
use rosenpass_to::{to, with_destination, To};
use std::result::Result; use std::result::Result;
use rosenpass_to::{to, To, with_destination};
#[derive(PartialEq, Eq, Debug, Default)] #[derive(PartialEq, Eq, Debug, Default)]
struct InvalidFloat; struct InvalidFloat;
@@ -380,8 +407,8 @@ Condensation is implemented through a trait called CondenseBeside ([local](Conde
If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules. If you can not implement this trait because its for an external type (see [orphan rule](https://doc.rust-lang.org/book/ch10-02-traits.html#implementing-a-trait-on-a-type)), this crate welcomes contributions of new Condensation rules.
```rust ```rust
use rosenpass_to::{To, with_destination, Beside, CondenseBeside};
use rosenpass_to::ops::copy_slice; use rosenpass_to::ops::copy_slice;
use rosenpass_to::{with_destination, Beside, CondenseBeside, To};
#[derive(PartialEq, Eq, Debug, Default)] #[derive(PartialEq, Eq, Debug, Default)]
struct MyTuple<Left, Right>(Left, Right); struct MyTuple<Left, Right>(Left, Right);
@@ -396,7 +423,10 @@ impl<Val, Right> CondenseBeside<Val> for MyTuple<(), Right> {
} }
fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a fn copy_slice_and_return_something<'a, T, U>(src: &'a [T], something: U) -> impl To<[T], U> + 'a
where T: Copy, U: 'a { where
T: Copy,
U: 'a,
{
with_destination(move |dst: &mut [T]| { with_destination(move |dst: &mut [T]| {
copy_slice(src).to(dst); copy_slice(src).to(dst);
something something
@@ -417,7 +447,7 @@ Using `with_destination(...)` is convenient, but since it uses closures it resul
Implementing the ToTrait manual is the right choice for library use cases. Implementing the ToTrait manual is the right choice for library use cases.
```rust ```rust
use rosenpass_to::{to, To, with_destination}; use rosenpass_to::{to, with_destination, To};
struct TryCopySliceSource<'a, T: Copy> { struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T], src: &'a [T],
@@ -425,17 +455,20 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> { fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len()) (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
.then(|| dst.copy_from_slice(self.src))
} }
} }
fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T> fn try_copy_slice<'a, T>(src: &'a [T]) -> TryCopySliceSource<'a, T>
where T: Copy { where
T: Copy,
{
TryCopySliceSource { src } TryCopySliceSource { src }
} }
let mut dst = try_copy_slice(b"Hello World").collect::<[u8; 11]>().unwrap(); let mut dst = try_copy_slice(b"Hello World")
.collect::<[u8; 11]>()
.unwrap();
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---"))); assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
``` ```
@@ -445,8 +478,8 @@ assert_eq!(None, to(&mut dst[..], try_copy_slice(b"---")));
Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation. Destinations can also be used with methods. This example demonstrates using destinations in an extension trait for everything that implements `Borrow<[T]>` for any `T` and a concrete `To` trait implementation.
```rust ```rust
use rosenpass_to::{to, with_destination, To};
use std::borrow::Borrow; use std::borrow::Borrow;
use rosenpass_to::{to, To, with_destination};
struct TryCopySliceSource<'a, T: Copy> { struct TryCopySliceSource<'a, T: Copy> {
src: &'a [T], src: &'a [T],
@@ -454,24 +487,24 @@ struct TryCopySliceSource<'a, T: Copy> {
impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> { impl<'a, T: Copy> To<[T], Option<()>> for TryCopySliceSource<'a, T> {
fn to(self, dst: &mut [T]) -> Option<()> { fn to(self, dst: &mut [T]) -> Option<()> {
(self.src.len() == dst.len()) (self.src.len() == dst.len()).then(|| dst.copy_from_slice(self.src))
.then(|| dst.copy_from_slice(self.src))
} }
} }
trait TryCopySliceExt<'a, T: Copy> { trait TryCopySliceExt<'a, T: Copy> {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>; fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T>;
} }
impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref { impl<'a, T: 'a + Copy, Ref: 'a + Borrow<[T]>> TryCopySliceExt<'a, T> for Ref {
fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> { fn try_copy_slice(&'a self) -> TryCopySliceSource<'a, T> {
TryCopySliceSource { TryCopySliceSource { src: self.borrow() }
src: self.borrow()
}
} }
} }
let mut dst = b"Hello World".try_copy_slice().collect::<[u8; 11]>().unwrap(); let mut dst = b"Hello World"
.try_copy_slice()
.collect::<[u8; 11]>()
.unwrap();
assert_eq!(&dst[..], b"Hello World"); assert_eq!(&dst[..], b"Hello World");
assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice())); assert_eq!(None, to(&mut dst[..], b"---".try_copy_slice()));
``` ```