简体   繁体   中英

What is the Rust equivalent of a char buffer / ASCII string on the stack?

I'm trying to find the Rust equivalent of having a ASCII string buffer on the stack to have the same efficiency as plain C code has.

Here an example on what I mean with a simplified toy exercise: the goal is to generate a random-content and random-length ASCII string that is at most 50 characters long. Thus I keep a char buffer on the stack that is used to iteratively construct the string. Once finished, the string is copied onto the heap with the just-right malloc size and returned to the user.

#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdio.h>

#define ASCII_PRINTABLE_FIRST ' '
#define ASCII_PRINTABLE_AMOUNT 95
#define MAX_LEN 50
#define MAX_LEN_WITH_TERM (MAX_LEN + 1)

char* generate_string(void) {
    char buffer[MAX_LEN_WITH_TERM];
    srand((unsigned) time(NULL));
    // Generate random string length
    const int len = rand() % MAX_LEN_WITH_TERM;
    int i;
    for (i = 0; i < len; i++) {
        // Fill with random ASCII printable character
        buffer[i] = (char)
            ((rand() % ASCII_PRINTABLE_AMOUNT) + ASCII_PRINTABLE_FIRST);
    }
    buffer[i] = '\0';
    return strdup(buffer);
}

int main(void) {
    printf("Generated string: %s\n", generate_string());
    return 0;
}

What I explored so far:

  • Using a buffer String::with_capacity(50) or BytesMut , but that allocates the buffer on the heap, which I would like to avoid. Sure, it's premature optimisation, but as an optimisation exercise let's image me calling generate_string() a billion times. That is a billion malloc calls to allocate the buffer. I don't want to use static memory.
  • Using a an array of chars on the stack, but it consumes 4x the space for just ASCII characters

What are your suggestions?

EDIT:

  1. Yes, it leaks memory. That't not the point of my question, unless you want much longer snippets of code.
  2. Yes, it has insecure random characters. That's not the point of my question.
  3. Why would I allocate the buffer on the heap once per generate_string() call? To make the function self contained, stateless and without static memory. It does not require a pre-allocated buffer externally.

The Rust type that is equivalent to C's char is u8 , so the equivalent to a char buffer on the stack is an u8 array.

let mut buf = [0u8; 20];

for i in 0..20 {
    buf[i] = b'a' + i as u8;
}

To obtain a &str slice that points into the stack buffer, you can use std::str::from_utf8 , which performs a UTF-8 check and returns the pointer if it is valid UTF-8.

fn takes_a_string(a: &str) {
    println!("{}", a);
}

fn main() {
    let mut buf = [0u8; 20];
    
    for i in 0..20 {
        buf[i] = b'a' + i as u8;
    }
    
    // This calls takes_a_string with a reference to the stack buffer.
    takes_a_string(std::str::from_utf8(&buf).unwrap());
}
abcdefghijklmnopqrst

You can generate a random length u8 array (stored on the stack) and only allocate memory on the heap when you convert it to a String using the from_utf8 method. Example:

use rand::prelude::*;

const MAX_LEN: usize = 50;
const ASCII_START: u8 = 32;
const ASCII_END: u8 = 127;

fn generate_string() -> String {
    let mut buffer = [0; MAX_LEN];
    let mut rng = rand::thread_rng();
    let buffer_len = rng.gen_range(0, MAX_LEN);
    for i in 0..buffer_len {
        buffer[i] = rng.gen_range(ASCII_START, ASCII_END);
    }
    String::from_utf8((&buffer[0..buffer_len]).to_vec()).unwrap()
}

fn main() {
    for _ in 0..5 {
       dbg!(generate_string()); 
    }
}

playground

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM