How to extend TupleStruct behavior using traits?

I want to create TerminatedString struct that will behave like String, but will add zero to the end of the string. So it should behave like this:

#[derive(Debug, Default, Clone)]
pub struct TerminatedString(String);

// creation
let str = TerminatedString(String::from("Test"));

// output
println!("{:?}", str); // should display: Test0

Could somebody explain, does it possible to implement such behavior using traits ? Or I should use simple struct instead and implement this behavior in ::new method ?

Generally, it's not possible. Are you trying to reinvent CString?

3 Likes

Implement Debug yourself!

impl core::fmt::Debug for TerminatedString {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        write!(f, "{}0", self.0)
    }
}

If I correctly understood what you needed

1 Like

well, I need it to be used as separate field type in another struct (generated by macro):

packet! {
    @option[opcode=Opcode::CMSG_JOIN_CHANNEL]
    pub struct Outcome {
        channel_id: u32,
        unknown: u8,
        unknown1: u8,
        channel_name: TerminatedString,
    }
}

I implemented BinaryConverter trait for each type I use on struct fields, so when macro generates the struct fields, it calls the method provided by BinaryConverter. Not sure if Debug will works here, so I am sorry for confusion.

Just in case this is full code of my macro:

#[macro_export]
macro_rules! packet {
    (
        $(@option[opcode=$opcode_value:expr])?
        $(@option[no_size:$no_size:expr])?
        $(@option[compressed:$compressed_value:expr])?

        $(#[$outer:meta])*
        $vis:vis struct $PacketStruct:ident {
            $($field_vis:vis $field_name:ident: $field_type:ty),*$(,)?
        }

        $($PacketStructImpl: item)*
    ) => {
        $(#[$outer])*
        #[derive(Clone, Debug, Default)]
        $vis struct $PacketStruct {
            $($field_vis $field_name: $field_type),*
        }

        $($PacketStructImpl)*

        impl $PacketStruct {
            // income
            pub fn from_binary(buffer: &Vec<u8>) -> Self {
                let mut omit_bytes: usize = $crate::crypto::decryptor::INCOMING_HEADER_LENGTH;
                $(
                    if $no_size {
                        // because no_size packets are only on login server
                        omit_bytes = 1;
                    }
                )?
                $(
                    if $compressed_value {
                        // 4 bytes uncompressed + 2 bytes used by zlib
                        omit_bytes += 6;
                    }
                )?

                let mut internal_buffer: Vec<u8> = Vec::new();
                $(
                    if $compressed_value {
                        let data = &buffer[omit_bytes..];
                        let mut decoder = flate2::read::DeflateDecoder::new(data);
                        std::io::Read::read_to_end(&mut decoder, &mut internal_buffer).unwrap();
                    }
                )?

                let buffer = if internal_buffer.is_empty() {
                    buffer[omit_bytes..].to_vec()
                } else {
                    internal_buffer
                };

                let mut reader = std::io::Cursor::new(&buffer);

                Self {
                    $(
                        $field_name: $crate::traits::binary_converter::BinaryConverter::read_from(
                            &mut reader
                        ).unwrap()
                    ),*
                }
            }

            // outcome
            pub fn to_binary(&mut self) -> Vec<u8> {
                let mut packet = Vec::new();
                $(
                    $crate::traits::binary_converter::BinaryConverter::write_into(
                        &mut self.$field_name,
                        &mut packet
                    ).unwrap();
                )*
                let header = Self::_build_header(&packet);
                [header, packet].concat()
            }

            fn _build_header(body: &Vec<u8>) -> Vec<u8> {
                let mut header: Vec<u8> = Vec::new();
                let mut no_size = false;
                $(
                    no_size = $no_size;
                )?
                if no_size {
                    $(
                        byteorder::WriteBytesExt::write_u8(
                            &mut header,
                            $opcode_value as u8
                        ).unwrap();
                    )?
                } else {
                    $(
                        let size = body.len() + $crate::crypto::encryptor::OUTCOMING_OPCODE_LENGTH;
                        byteorder::WriteBytesExt::write_u16::<byteorder::BigEndian>(
                            &mut header,
                            size as u16,
                        ).unwrap();
                        byteorder::WriteBytesExt::write_u32::<byteorder::LittleEndian>(
                            &mut header,
                            $opcode_value as u32
                        ).unwrap();
                    )?
                }

                header
            }

            $(
                pub fn unpack(&mut self) -> $crate::types::PacketOutcome {
                    ($opcode_value as u32, self.to_binary())
                }
            )?
        }
    };
}

Probably it would works with just String, but some packets contains strings without null-termination, so I have no choice than implement separate type for null-termination.

Note that "abc0" is not a null-terminated string, "abc\0" is. '0' is 48 in ASCII/ANSI, while '\0' is 0. Iā€™d recommend to use something like CString from alloc/std crate.

2 Likes