I receive a long string (several gigabytes) as chunks of [u8]s in a Crossbeam channel. I want to break it down to lines. How do I turn these chunks into a BufRead?
fn foo(recv: crossbeam_channel::Receiver<Vec<u8>>) {
let mut buf_read: dyn std::io::BufRead = WHAT_COMES_HERE(recv); // <----
for line in buf_read.lines() {
// ...
}
}
I make these chunks on another thread since they are CPU-intensive to make. I could use something else than Vec<u8> if it makes more sense.
I don't think there is anything builtin, but it shouldn't be too difficult to write yourself. For example something like this:
use crossbeam_channel; // 0.5.4
use std::cmp::min;
use std::io::BufRead;
struct CrossbeamReader {
recv: crossbeam_channel::Receiver<Vec<u8>>,
offset: usize,
buffer: Vec<u8>,
}
impl CrossbeamReader {
fn new (recv: crossbeam_channel::Receiver<Vec<u8>>) -> Self
{
CrossbeamReader { recv, offset: 0, buffer: vec![], }
}
}
impl std::io::Read for CrossbeamReader {
fn read (&mut self, buf: &mut [u8]) -> std::io::Result<usize>
{
while self.offset >= self.buffer.len() {
self.buffer = match self.recv.recv() {
Ok (v) => v,
Err (_) => return Ok (0), // TODO: error handling
};
self.offset = 0;
}
let size = min (buf.len(), self.buffer.len() - self.offset);
buf[..size].copy_from_slice (&self.buffer[self.offset .. self.offset + size]);
self.offset += size;
Ok (size)
}
}
pub fn foo(recv: crossbeam_channel::Receiver<Vec<u8>>) {
let buf_read = std::io::BufReader::new (CrossbeamReader::new (recv));
for _line in buf_read.lines() {
// ...
}
}
Playground
Related
I want to config this file to add a number of partition option here as by default it is creating only 1 partition , but I need 10 for my data .
I dont have much knowledge of rdkafka library in rust , as I am directly using this plugin file
Can anyone guide me where can I find solution to this or what direction .
Thanks
use rdkafka::error::{KafkaError};
use rdkafka::{ClientConfig};
use rdkafka::producer::{FutureProducer, FutureRecord};
use std::fmt::Error;
use std::os::raw::{c_char, c_int, c_void};
use std::sync::mpsc::TrySendError;
use suricata::conf::ConfNode;
use suricata::{SCLogError, SCLogNotice};
const DEFAULT_BUFFER_SIZE: &str = "65535";
const DEFAULT_CLIENT_ID: &str = "rdkafka";
#[derive(Debug, Clone)]
struct ProducerConfig {
brokers: String,
topic: String,
client_id: String,
buffer: usize,
}
impl ProducerConfig {
fn new(conf: &ConfNode) -> Result<Self,Error> {
let brokers = if let Some(val) = conf.get_child_value("brokers"){
val.to_string()
}else {
SCLogError!("brokers parameter required!");
panic!();
};
let topic = if let Some(val) = conf.get_child_value("topic"){
val.to_string()
}else {
SCLogError!("topic parameter required!");
panic!();
};
let client_id = conf.get_child_value("client-id").unwrap_or(DEFAULT_CLIENT_ID);
let buffer_size = match conf
.get_child_value("buffer-size")
.unwrap_or(DEFAULT_BUFFER_SIZE)
.parse::<usize>()
{
Ok(size) => size,
Err(_) => {
SCLogError!("invalid buffer-size!");
panic!();
},
};
let config = ProducerConfig {
brokers: brokers.into(),
topic: topic.into(),
client_id: client_id.into(),
buffer: buffer_size,
};
Ok(config)
}
}
struct KafkaProducer {
producer: FutureProducer,
config: ProducerConfig,
rx: std::sync::mpsc::Receiver<String>,
count: usize,
}
impl KafkaProducer {
fn new(
config: ProducerConfig,
rx: std::sync::mpsc::Receiver<String>,
) -> Result<Self,KafkaError> {
let producer: FutureProducer = ClientConfig::new()
.set("bootstrap.servers", &config.brokers)
.set("client.id",&config.client_id)
.set("message.timeout.ms", "5000")
.create()?;
Ok(Self {
config,
producer,
rx,
count: 0,
})
}
fn run(&mut self) {
// Get a peekable iterator from the incoming channel. This allows us to
// get the next message from the channel without removing it, we can
// then remove it once its been sent to the server without error.
//
// Not sure how this will work with pipe-lining tho, will probably have
// to do some buffering here, or just accept that any log records
// in-flight will be lost.
let mut iter = self.rx.iter().peekable();
loop {
if let Some(buf) = iter.peek() {
self.count += 1;
if let Err(err) = self.producer.send_result(
FutureRecord::to(&self.config.topic)
.key("")
.payload(&buf),
) {
SCLogError!("Failed to send event to Kafka: {:?}", err);
break;
} else {
// Successfully sent. Pop it off the channel.
let _ = iter.next();
}
} else {
break;
}
}
SCLogNotice!("Producer finished: count={}", self.count,);
}
}
struct Context {
tx: std::sync::mpsc::SyncSender<String>,
count: usize,
dropped: usize,
}
unsafe extern "C" fn output_open(conf: *const c_void, init_data: *mut *mut c_void) -> c_int {
// Load configuration.
let config = ProducerConfig::new(&ConfNode::wrap(conf)).unwrap();
let (tx, rx) = std::sync::mpsc::sync_channel(config.buffer);
let mut kafka_producer = match KafkaProducer::new(config, rx) {
Ok(producer) => {
SCLogNotice!(
"KafKa Producer initialize success with brokers:{:?} | topic: {:?} | client_id: {:?} | buffer-size: {:?}",
producer.config.brokers,
producer.config.topic,
producer.config.client_id,
producer.config.buffer
);
producer
}
Err(err) => {
SCLogError!("Failed to initialize Kafka Producer: {:?}", err);
panic!()
}
};
let context = Context {
tx,
count: 0,
dropped: 0,
};
std::thread::spawn(move || {kafka_producer.run()});
// kafka_producer.run();
*init_data = Box::into_raw(Box::new(context)) as *mut _;
0
}
unsafe extern "C" fn output_close(init_data: *const c_void) {
let context = Box::from_raw(init_data as *mut Context);
SCLogNotice!(
"Kafka produce finished: count={}, dropped={}",
context.count,
context.dropped
);
std::mem::drop(context);
}
unsafe extern "C" fn output_write(
buffer: *const c_char,
buffer_len: c_int,
init_data: *const c_void,
) -> c_int {
let context = &mut *(init_data as *mut Context);
let buf = if let Ok(buf) = ffi::str_from_c_parts(buffer, buffer_len) {
buf
} else {
return -1;
};
context.count += 1;
if let Err(err) = context.tx.try_send(buf.to_string()) {
context.dropped += 1;
match err {
TrySendError::Full(_) => {
SCLogError!("Eve record lost due to full buffer");
}
TrySendError::Disconnected(_) => {
SCLogError!("Eve record lost due to broken channel{}",err);
}
}
}
00
}
unsafe extern "C" fn init_plugin() {
let file_type =
ffi::SCPluginFileType::new("kafka", output_open, output_close, output_write);
ffi::SCPluginRegisterFileType(file_type);
}
#[no_mangle]
extern "C" fn SCPluginRegister() -> *const ffi::SCPlugin {
// Rust plugins need to initialize some Suricata internals so stuff like logging works.
suricata::plugin::init();
// Register our plugin.
ffi::SCPlugin::new("Kafka Eve Filetype", "GPL-2.0", "1z3r0", init_plugin)
}
For threaded applications, the Rust standard library provides std::sync::mpsc::sync_channel, a buffered channel which blocks on the reading end when the buffer is empty and blocks on the writing end when the buffer is full. In particular, if you set the buffer size to 0, then any read or write will block until there is a matching write or read.
For async code, there is futures::channel::mpsc::channel, but this does not have the same behavior. Here the minimum capacity is the number of senders on the channel, which is greater than 0. The sending end can still block (because it implements Sink, so we can use SinkExt::send and await it), but only after there's at least one thing already in the buffer.
I took a look to see if there were any packages that provide the functionality I'm looking for, but I could not find anything. Tokio provides lots of nice async synchronization primitives, but none of them did quite what I'm looking for. Plus, my program is going to run in the browser, so I don't think I'm able to use a runtime like Tokio. Does anyone know of a package that fits my use case? I would try to implement this myself, since this almost feels like the most minimal use case for the Sink and Stream traits, but even a minimal implementation of these traits seems like it would be really complicated. Thoughts?
Edit: here's a minimal example of what I mean:
fn main() {
let (tx, rx) = blocking_channel();
let ft = async move {
tx.send(3).await;
println!("message sent and received");
}
let fr = async move {
let msg = rx.recv().await;
println!("received {}", msg);
}
block_on(async { join!(ft, fr) });
}
In this example, whichever future runs first will yield to the other, and only print after both rx.recv and tx.send have been called. Obviously, the receiving end can only progress after tx.send has been called, but I want the less obvious behavior of the transmitting end also having to wait.
Interesting question. I don't think something like that already exists.
Here is a quick proof-of-concept prototype I wrote for that. It's not the prettiest, but it seems to work. There might be a better struct layout than just wrapping everything in a RefCell<Option<...>>, though. And I don't particularly like the sender_dropped and receiver_dropped variables.
Be sure to unittest it properly if used in production!!!
extern crate alloc;
use alloc::rc::Rc;
use core::cell::RefCell;
use core::pin::Pin;
use core::task::{Poll, Waker};
use futures::SinkExt;
use futures::StreamExt;
struct Pipe<T> {
send_waker: RefCell<Option<Waker>>,
receive_waker: RefCell<Option<Waker>>,
value: RefCell<Option<T>>,
sender_dropped: RefCell<bool>,
receiver_dropped: RefCell<bool>,
}
impl<T> Pipe<T> {
fn new() -> Rc<Pipe<T>> {
Rc::new(Self {
value: RefCell::new(None),
send_waker: RefCell::new(None),
receive_waker: RefCell::new(None),
sender_dropped: RefCell::new(false),
receiver_dropped: RefCell::new(false),
})
}
}
impl<T> Pipe<T> {
fn wake_sender(&self) {
if let Some(waker) = self.send_waker.replace(None) {
waker.wake();
}
}
fn wake_receiver(&self) {
if let Some(waker) = self.receive_waker.replace(None) {
waker.wake();
}
}
}
pub struct PipeSender<T> {
pipe: Rc<Pipe<T>>,
}
pub struct PipeReceiver<T> {
pipe: Rc<Pipe<T>>,
}
pub fn create_pipe<T>() -> (PipeSender<T>, PipeReceiver<T>) {
let pipe = Pipe::new();
(PipeSender { pipe: pipe.clone() }, PipeReceiver { pipe })
}
impl<T> futures::Sink<T> for PipeSender<T> {
type Error = ();
fn poll_ready(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> Poll<Result<(), Self::Error>> {
let result = if *self.pipe.receiver_dropped.borrow() {
Poll::Ready(Err(()))
} else if self.pipe.receive_waker.borrow().is_some() && self.pipe.value.borrow().is_none() {
Poll::Ready(Ok(()))
} else {
self.pipe.send_waker.replace(Some(cx.waker().clone()));
Poll::Pending
};
// Wake potential receiver
self.pipe.wake_receiver();
result
}
fn start_send(self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> {
let prev = self.pipe.value.replace(Some(item));
assert!(prev.is_none(), "A value got lost in the pipe.");
Ok(())
}
fn poll_flush(
self: Pin<&mut Self>,
_: &mut futures::task::Context<'_>,
) -> Poll<Result<(), Self::Error>> {
// Noop, start_send already completes the send
Poll::Ready(Ok(()))
}
fn poll_close(
self: Pin<&mut Self>,
_: &mut std::task::Context<'_>,
) -> Poll<Result<(), Self::Error>> {
// Noop, start_send already completes the send
Poll::Ready(Ok(()))
}
}
impl<T> futures::Stream for PipeReceiver<T> {
type Item = T;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
let result = {
let value = self.pipe.value.replace(None);
if let Some(value) = value {
Poll::Ready(Some(value))
} else if *self.pipe.sender_dropped.borrow() {
Poll::Ready(None)
} else {
self.pipe.receive_waker.replace(Some(cx.waker().clone()));
Poll::Pending
}
};
// Wake potential sender
self.pipe.wake_sender();
result
}
}
impl<T> Drop for PipeSender<T> {
fn drop(&mut self) {
self.pipe.sender_dropped.replace(true);
self.pipe.wake_receiver();
}
}
impl<T> Drop for PipeReceiver<T> {
fn drop(&mut self) {
self.pipe.receiver_dropped.replace(true);
self.pipe.wake_sender();
}
}
#[tokio::main]
async fn main() {
use std::time::Duration;
let (mut sender, mut receiver) = create_pipe();
tokio::join!(
async move {
for i in 0..5u32 {
println!("Sending {i} ...");
if let Err(_) = sender.send(i).await {
println!("Stream closed.");
break;
}
println!("Sent {i}.");
}
println!("Sender closed.");
},
async move {
println!("Attempting to receive ...");
while let Some(val) = receiver.next().await {
println!("Received: {val}");
println!("\n=== Waiting ... ===\n");
tokio::time::sleep(Duration::from_secs(1)).await;
println!("Attempting to receive ...");
}
println!("Receiver closed.");
}
);
}
Sending 0 ...
Attempting to receive ...
Sent 0.
Sending 1 ...
Received: 0
=== Waiting ... ===
Attempting to receive ...
Sent 1.
Sending 2 ...
Received: 1
=== Waiting ... ===
Attempting to receive ...
Sent 2.
Sending 3 ...
Received: 2
=== Waiting ... ===
Attempting to receive ...
Sent 3.
Sending 4 ...
Received: 3
=== Waiting ... ===
Attempting to receive ...
Sent 4.
Sender closed.
Received: 4
=== Waiting ... ===
Attempting to receive ...
Receiver closed.
I have an actix web service and would like to parse the contents of a multipart field while streaming with async-gcode and in addition store the contents e.g. in a database.
However, I have no clue how to feed in the stream to the Parser and at the same time collect the bytes into a Vec<u8> or a String.
The first problem I face is that field is a stream of actix::web::Bytes and not of u8.
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
while let Some(chunk) = field.next().await {
contents.append(&mut chunk.unwrap().to_vec());
// already parse the contents
// and additionally store contents somewhere
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Any hint or suggestion is very much appreciated.
One of the options is to wrap field in a struct and implement Stream trait for it.
use actix_web::{HttpRequest, HttpResponse, Error};
use futures_util::stream::Stream;
use std::pin::Pin;
use actix_multipart::{Multipart, Field};
use futures::stream::{self, StreamExt};
use futures_util::TryStreamExt;
use std::task::{Context, Poll};
use async_gcode::{Parser, Error as PError};
use bytes::BytesMut;
use std::cell::RefCell;
pub struct Wrapper {
field: Field,
buffer: RefCell<BytesMut>,
index: usize,
}
impl Wrapper {
pub fn new(field: Field, buffer: RefCell<BytesMut>) -> Self {
buffer.borrow_mut().truncate(0);
Wrapper {
field,
buffer,
index: 0
}
}
}
impl Stream for Wrapper {
type Item = Result<u8, PError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<u8, PError>>> {
if self.index == self.buffer.borrow().len() {
match Pin::new(&mut self.field).poll_next(cx) {
Poll::Ready(Some(Ok(chunk))) => self.buffer.get_mut().extend_from_slice(&chunk),
Poll::Pending => return Poll::Pending,
Poll::Ready(None) => return Poll::Ready(None),
Poll::Ready(Some(Err(_))) => return Poll::Ready(Some(Err(PError::BadNumberFormat/* ??? */)))
};
} else {
let b = self.buffer.borrow()[self.index];
self.index += 1;
return Poll::Ready(Some(Ok(b)));
}
Poll::Ready(None)
}
}
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
while let Ok(Some(field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let mut contents: RefCell<BytesMut> = RefCell::new(BytesMut::new());
let mut w = Wrapper::new(field, contents.clone());
let mut p = Parser::new(w);
while let Some(res) = p.next().await {
// Do something with results
};
// Do something with the buffer
let a = contents.get_mut()[0];
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
Copying the Bytes from the Field won't be necessary when
Bytes::try_unsplit will be implemented. (https://github.com/tokio-rs/bytes/issues/287)
The answer from dmitryvm (thanks for your effort) showed me that there are actually two problems. At first, flatten the Bytes into u8's and, secondly, to "split" the stream into a buffer for later storage and the async-gcode parser.
This shows how I solved it:
#[post("/upload")]
pub async fn upload_job(
mut payload: Multipart,
) -> Result<HttpResponse, Error> {
let mut contents : Vec<u8> = Vec::new();
while let Ok(Some(mut field)) = payload.try_next().await {
let content_disp = field.content_disposition().unwrap();
match content_disp.get_name().unwrap() {
"file" => {
let field_stream = field
.map_err(|_| async_gcode::Error::BadNumberFormat) // Translate error
.map_ok(|y| { // Translate Bytes into stream with Vec<u8>
contents.extend_from_slice(&y); // Copy and store for later usage
stream::iter(y).map(Result::<_, async_gcode::Error>::Ok)
})
.try_flatten(); // Flatten the streams of u8's
let mut parser = Parser::new(field_stream);
while let Some(gcode) = parser.next().await {
// Process result from parser
}
}
_ => (),
}
}
Ok(HttpResponse::Ok().finish())
}
I am trying to play a WAV file using rust-sdl2.
I found AudioSpecWAV, but none of the audio initialisation methods seem to take it as a type, and it doesn't implement AudioCallback. I tried implementing this myself with my own callback looking something like:
struct MyWav {
wav: AudioSpecWAV,
volume: f32,
pos: usize,
}
impl AudioCallback for MyWav {
type Channel = f32;
fn callback(&mut self, out: &mut [f32]) {
for x in out.iter_mut() {
*x = match self.wav.buffer().get(self.pos) {
Some(v) => { self.pos += 1; v as f32 },
None => { 0.0 }
}
}
}
}
... but I don't know how to work around the following error I get:
the traitcore::marker::Sync is not implemented for the type *mut u8
This seems to be the audio_buf field of AudioSpecWAV, but if that's not Sync how am I supposed to pass a buffer to the callback?
(for reference, here is an example of playing a generated sound)
AudioCallback requires implementers to be Send. You could do that by wrapping AudioSpecWAV in a struct and doing an unsafe impl for Send on that struct, or you could copy the data. Since you shouldn't typically use unsafe unless you know what you are doing is actually safe, you may want to look at the copy approach.
Here is an example of both approaches:
extern crate sdl2;
use std::thread::{self};
use sdl2::{Sdl};
use sdl2::audio::{self, AudioSpecDesired, AudioSpecWAV, AudioCallback, AudioDevice};
//----------------------------------------------------------------------------//
struct CopiedData {
bytes: Vec<u8>,
position: usize
}
impl AudioCallback for CopiedData {
type Channel = u8;
fn callback(&mut self, data: &mut [u8]) {
let (start, end) = (self.position, self.position + data.len());
self.position += data.len();
let audio_data = &self.bytes[start..end];
for (src, dst) in audio_data.iter().zip(data.iter_mut()) {
*dst = *src;
}
}
}
//----------------------------------------------------------------------------//
struct WrappedData {
audio: AudioSpecWAV,
position: usize
}
impl AudioCallback for WrappedData {
type Channel = u8;
fn callback(&mut self, data: &mut [u8]) {
let (start, end) = (self.position, self.position + data.len());
self.position += data.len();
let audio_data = &self.audio.buffer()[start..end];
for (src, dst) in audio_data.iter().zip(data.iter_mut()) {
*dst = *src;
}
}
}
unsafe impl Send for WrappedData { }
//----------------------------------------------------------------------------//
pub fn main() {
let sdl_context = sdl2::init().unwrap();
let audio_system = sdl_context.audio().unwrap();
let audio_spec = AudioSpecDesired{ freq: None, channels: None, samples: None };
let audio_wav = AudioSpecWAV::load_wav("test.wav").unwrap();
let copied_data = CopiedData{ bytes: audio_wav.buffer().to_vec(), position: 0 };
//let wrapped_data = WrappedData{ audio: audio_wav, position: 0 };
let audio_device = audio_system.open_playback(None, audio_spec, move |spec| {
copied_data
}).unwrap();
audio_device.resume();
thread::sleep_ms(5000);
}
Note: The WAV I was playing was quite loud (to the point where it sounded distorted) and I am not a sound guy so I am not sure if that had something to do with my code or the WAV file I was using in general.
Let's say that we have the following C-code (assume that srclen == dstlen and the length is divisible by 64).
void stream(uint8_t *dst, uint8_t *src, size_t dstlen) {
int i;
uint8_t block[64];
while (dstlen > 64) {
some_function_that_initializes_block(block);
for (i=0; i<64; i++) {
dst[i] = ((src != NULL)?src[i]:0) ^ block[i];
}
dst += 64;
dstlen -= 64;
if (src != NULL) { src += 64; }
}
}
That is a function that takes a source and a destination and xors source with some value that
the function computes. When source is set to a NULL-pointer dst is just the computed value.
In rust it is quite simple to do this when src cannot be null, we can do something like:
fn stream(dst: &mut [u8], src: &[u8]) {
let mut block = [0u8, ..64];
for (dstchunk, srcchunk) in dst.chunks_mut(64).zip(src.chunks(64)) {
some_function_that_initializes_block(block);
for (d, (&s, &b)) in dstchunk.iter_mut().zip(srcchunk.iter().zip(block.iter())) {
*d = s ^ b;
}
}
}
However let us assume that we want to be able to mimic the original C-function. Then we would like to do something like:
fn stream(dst: &mut[u8], osrc: Option<&[u8]>) {
let srciter = match osrc {
None => repeat(0),
Some(src) => src.iter()
};
// the rest of the code as above
}
Alas, this won't work since repeat(0) and src.iter() have different types. However it doesn't seem possible to solve this by using a trait object since we get a compiler error saying cannot convert to a trait object because trait 'core::iter::Iterator' is not object safe. (also there is no function in the standard library that chunks an iterator).
Is there any nice way to solve this, or should I just duplicate the code in each arm of the match statement?
Instead of repeating the code in each arm, you can call a generic inner function:
fn stream(dst: &mut[u8], osrc: Option<&[u8]>) {
fn inner<T>(dst: &mut[u8], srciter: T) where T: Iterator<u8> {
let mut block = [0u8, ..64];
//...
}
match osrc {
None => inner(dst, repeat(0)),
Some(src) => inner(dst, src.iter().map(|a| *a))
}
}
Note the additional map to make both iterators compatible (Iterator<u8>).
As you mentioned, Iterator doesn't have a built-in way to do chunking. Let's incorporate Vladimir's solution and use an iterator over chunks:
fn stream(dst: &mut[u8], osrc: Option<&[u8]>) {
const CHUNK_SIZE: uint = 64;
fn inner<'a, T>(dst: &mut[u8], srciter: T) where T: Iterator<&'a [u8]> {
let mut block = [0u8, ..CHUNK_SIZE];
for (dstchunk, srcchunk) in dst.chunks_mut(CHUNK_SIZE).zip(srciter) {
some_function_that_initializes_block(block);
for (d, (&s, &b)) in dstchunk.iter_mut().zip(srcchunk.iter().zip(block.iter())) {
*d = s ^ b;
}
}
}
static ZEROES: &'static [u8] = &[0u8, ..CHUNK_SIZE];
match osrc {
None => inner(dst, repeat(ZEROES)),
Some(src) => inner(dst, src.chunks(CHUNK_SIZE))
}
}
Unfortunately, it is impossible to use different iterators directly or with trait objects (which have recently been changed to disallow instantiation of trait objects with inappropriate methods i.e. ones which use Self type in their signature). There is a workaround for your particular case, however. Just use enums:
fn stream(dst: &mut [u8], src: Option<&[u8]>) {
static EMPTY: &'static [u8] = &[0u8, ..64]; // '
enum DifferentIterators<'a> { // '
FromSlice(std::slice::Chunks<'a, u8>), // '
FromRepeat(std::iter::Repeat<&'a [u8]>) // '
}
impl<'a> Iterator<&'a [u8]> for DifferentIterators<'a> { // '
#[inline]
fn next(&mut self) -> Option<&'a [u8]> { // '
match *self {
FromSlice(ref mut i) => i.next(),
FromRepeat(ref mut i) => i.next()
}
}
}
let srciter = match src {
None => FromRepeat(repeat(EMPTY)),
Some(src) => FromSlice(src.chunks(64))
};
let mut block = [0u8, ..64];
for (dstchunk, srcchunk) in dst.chunks_mut(64).zip(srciter) {
some_function_that_initializes_block(block);
for (d, (&s, &b)) in dstchunk.iter_mut().zip(srcchunk.iter().zip(block.iter())) {
*d = s ^ b;
}
}
}
This is a lot of code, unfortunately, but in return it is more safe and less error-prone than the C version. It is also possible to optimize it in order not to require repeat() at all:
fn stream(dst: &mut [u8], src: Option<&[u8]>) {
static EMPTY: &'static [u8] = &[0u8, ..64]; // '
enum DifferentIterators<'a> { // '
FromSlice(std::slice::Chunks<'a, u8>), // '
AlwaysZeros
}
impl<'a> Iterator<&'a [u8]> for DifferentIterators<'a> { // '
#[inline]
fn next(&mut self) -> Option<&'a [u8]> { // '
match *self {
FromSlice(ref mut i) => i.next(),
AlwaysZeros => Some(STATIC),
}
}
}
let srciter = match src {
None => AlwaysZeros,
Some(src) => FromSlice(src.chunks(64))
};
let mut block = [0u8, ..64];
for (dstchunk, srcchunk) in dst.chunks_mut(64).zip(srciter) {
some_function_that_initializes_block(block);
for (d, (&s, &b)) in dstchunk.iter_mut().zip(srcchunk.iter().zip(block.iter())) {
*d = s ^ b;
}
}
}