Skip to content

Expand impl Encode for Vec<String> to impl Encode for Vec<E: Encode> #314

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
Eugeny opened this issue Nov 9, 2024 · 5 comments · May be fixed by #377
Open

Expand impl Encode for Vec<String> to impl Encode for Vec<E: Encode> #314

Eugeny opened this issue Nov 9, 2024 · 5 comments · May be fixed by #377

Comments

@Eugeny
Copy link
Contributor

Eugeny commented Nov 9, 2024

Would be useful for Vec<&str> and key exchange Vec<Algorithm> lists for example.

@tarcieri
Copy link
Member

@Eugeny can you open a PR?

@Eugeny
Copy link
Contributor Author

Eugeny commented Jan 21, 2025

I would but my PRs from November are still hanging open :(

@tarcieri
Copy link
Member

I will get to them soon, I am just going through a very, very large backlog

@tsurdevson
Copy link
Contributor

I had a go at implementing this, adding a very slightly modified version of the Vec<String> impl like so:

#[cfg(feature = "alloc")]
impl<E: Encode> Encode for Vec<E> {
    fn encoded_len(&self) -> Result<usize, Error> {
        self.iter().try_fold(4usize, |acc, e| {
            acc.checked_add(e.encoded_len()?).ok_or(Error::Length)
        })
    }

    fn encode(&self, writer: &mut impl Writer) -> Result<(), Error> {
        self.encoded_len()?
            .checked_sub(4)
            .ok_or(Error::Length)?
            .encode(writer)?;

        for entry in self {
            entry.encode(writer)?;
        }

        Ok(())
    }
}

Seems straight forward. However, this collides with the Vec<u8> impl, so adding this would require removing the spesific impl for Vec<u8>. I set up a very small benchmark to see if this would significantly impact the performance of encoding Vec<u8>:

use criterion::{Criterion, criterion_group, criterion_main};
use ssh_encoding::Encode;
use std::hint::black_box;

fn encode_to_vec(bytes: &Vec<u8>, vec: &mut Vec<u8>) {
    bytes.encode(vec).unwrap();
}

fn criterion_benchmark(c: &mut Criterion) {
    let mut buf = Vec::new();
    let bytes = vec![0u8; 1024];
    c.bench_function("encode byte slice", |b| {
        b.iter(|| encode_to_vec(black_box(&bytes), black_box(&mut buf)));
    });
}

Running this benchmark before and after applying the patch results in a very significant difference.

encode byte slice       time:   [4.3553 µs 4.3709 µs 4.3887 µs]
                        change: [+308.72% +433.12% +589.52%] (p = 0.00 < 0.05)
                        Performance has regressed.

Unless the generic implementation can be made much smarter than what I did, I don't think it would be sensible to make this change.

Perhaps the correct thing to do is instead to add more impls, and/or slightly less generic ones that don't collide with the basic ones that are already there.

@tarcieri
Copy link
Member

tarcieri commented Jun 9, 2025

I think some marker trait which denotes that encoding a message this way is actually valid for a given type would be helpful, and may even get rid of the impl conflicts

tsurdevson added a commit to tsurdevson/SSH that referenced this issue Jun 10, 2025
This is in turn used to implement `Encode` for slices of string-like
types. Downstream users could implement the marker trait for their own
types and get the same behavior for slices of their own types.

Closes RustCrypto#314.
@tsurdevson tsurdevson linked a pull request Jun 10, 2025 that will close this issue
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants