1

I need to create packet to send to the server. For this purpose I use vector with byteorder crate. When I try to append string, Rust compiler tells I use unsafe function and give me an error.

use byteorder::{LittleEndian, WriteBytesExt};

fn main () {
    let login = "test";
    let packet_length = 30 + (login.len() as i16);

    let mut packet = Vec::new();
    packet.write_u8(0x00);
    packet.write_i16::<LittleEndian>(packet_length);
    packet.append(&mut Vec::from(String::from("game name ").as_bytes_mut()));

    // ... rest code
}

The error is:

packet.append(&mut Vec::from(String::from("game name ").as_bytes_mut()));
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ call to unsafe function

This is playground to reproduce: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=381c6d14660d47beaece15d068b3dc6a

What is the correct way to insert some string as bytes into vector ?

E_net4
  • 27,810
  • 13
  • 101
  • 139
Sergio Ivanuzzo
  • 1,820
  • 4
  • 29
  • 59
  • 1
    *A* solution is to call the function in an unsafe context, but the *real* solution is to find a way to do what you want in safe Rust. `as_bytes_mut` is unsafe because a `String` is always *always* __*always*__ assumed to be valid UTF-8, and modifying the byte array directly could break that promise. Do you *need* mutable access to the byte array? – Julia May 24 '22 at 07:58
  • 1
    If you don’t need mutable access (and I don’t think the snippet you included requires it), you can use `as_bytes`, which is perfectly safe. – Julia May 24 '22 at 08:09

2 Answers2

4

The unsafe function called was as_bytes_mut(). This creates a mutable reference with exclusive access to the bytes representing the string, allowing you to modify them. You do not really need a mutable reference in this case, as_bytes() would have sufficed.

However, there is a more idiomatic way. Vec<u8> also functions as a writer (it implements std::io::Write), so you can use one of its methods, or even the write! macro, to write encoded text on it.

use std::io::Write;
use byteorder::{LittleEndian, WriteBytesExt};

fn main () -> Result<(), std::io::Error> {
    let login = "test";
    let packet_length = 30 + (login.len() as i16);
    let mut packet = Vec::new();
    packet.write_u8(0x00)?;
    packet.write_i16::<LittleEndian>(packet_length)?;
    let game_name = String::from("game name");
    write!(packet, "{} ", game_name)?;

    Ok(())
}

Playground

See also:

E_net4
  • 27,810
  • 13
  • 101
  • 139
  • With the write macros do we not need to keep track of the current position in the vector? Or what is doing that for you? – uriDium Nov 28 '22 at 08:10
  • @uriDium The implementation of `Write` for `Vec` appends incoming bytes to the end of the vector, which is known by the vector value itself. https://doc.rust-lang.org/std/vec/struct.Vec.html#impl-Write-for-Vec%3Cu8%2C%20A%3E – E_net4 Nov 28 '22 at 16:41
1

You can use .extend() on the Vec and pass in the bytes representation of the String:

use byteorder::{LittleEndian, WriteBytesExt};

fn main() {
    let login = "test";
    let packet_length = 30 + (login.len() as i16);
    let mut packet = Vec::new();
    packet.write_u8(0x00);
    packet.write_i16::<LittleEndian>(packet_length);
    let string = String::from("game name ");
    packet.extend(string.as_bytes());
}

Playground

Dogbert
  • 212,659
  • 41
  • 396
  • 397