#Is this a valid use of unsafe?

84 messages · Page 1 of 1 (latest)

keen skiff
#

Is there a safe way to do the following without losing performance? All the safe variants required me to allocate/copy and return a String.

fn convert_seconds_to_hhmmssmmm(
    time_buffer: &mut [u8; 12],
    seconds: f64,
    millisecond_separator: u8,
) -> &str {
    let seconds_truncated = seconds as u64;
    let milliseconds = (seconds.fract() * 1000.0) as u64;
    let hours = seconds_truncated / 3600;
    let minutes = (seconds_truncated / 60) % 60;
    let seconds_remaining = seconds_truncated % 60;

    time_buffer[2] = b':';
    time_buffer[5] = b':';
    time_buffer[8] = millisecond_separator;

    time_buffer[0] += (hours / 10) as u8;
    time_buffer[1] += (hours % 10) as u8;
    time_buffer[3] += (minutes / 10) as u8;
    time_buffer[4] += (minutes % 10) as u8;
    time_buffer[6] += (seconds_remaining / 10) as u8;
    time_buffer[7] += (seconds_remaining % 10) as u8;
    time_buffer[9] += (milliseconds / 100) as u8;
    time_buffer[10] += ((milliseconds / 10) % 10) as u8;
    time_buffer[11] += (milliseconds % 10) as u8;

    unsafe { std::str::from_utf8_unchecked(time_buffer) }
}
```
karmic tusk
#

What are you expecting people to pass in for time_buffer?

keen skiff
quartz ether
#

i mean theres std::str::from_utf8

karmic tusk
#

Expecting them to pass you an empty array of bytes for you to write to and then return a reference to feels very strange to me

quartz ether
spiral cipher
#

It's not

#

Because the caller could pass in anything, and you could end up with invalid utf8

karmic tusk
#

Does the output &str's liftime get tied to time_buffer in any way? Or are you going to get a hanging reference if time_buffer gets deallocated before the &str

quartz ether
#

?eval b'0'

rare cliffBOT
#
48```
keen skiff
spiral cipher
karmic tusk
#

Neat

spiral cipher
#

The unsafe here is only to promise that the bytes are infact valid utf8

#

That's the only requirement for that function

#

Would it be possible to just return a [u8; 12]?

keen skiff
spiral cipher
quartz ether
#

tbh why not have a dedicated struct instead of a byte array

spiral cipher
#

Also I'm pretty sure the compiler can optimize out the utf8 check

quartz ether
#

you can also just impl Display instead of doing this

keen skiff
quartz ether
#

?

#

that doesnt really change anything with what i said

keen skiff
quartz ether
#

no

#

im asking why have a time_buffer byte array

keen skiff
#

The millisecond separator

quartz ether
#

a byte array isnt required for that

keen skiff
#

I want to do it without copying or allocating

quartz ether
#

yea, which doesnt require a byte array

keen skiff
#

afaik, Display only has methods that return a String, and thats an allocation.

keen skiff
#

String is stored on the heap no?

spiral cipher
spiral cipher
#

Infact it exists on core, where there is no heap

keen skiff
#

I’ll look into it

quartz ether
#
struct TimeRepr {
    hour: u8,
    minute: u8,
    second: u8,
    milliseconds: u8
}

impl TimeRepr {
    fn from_seconds(seconds: f64) -> Self {
        // parse seconds
    }
}

impl Display for TimeRepr {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "{}:{}:{}:{}", self.hour, self.minute, self.second, self.milliseconds);
    }
}
#

tada

keen skiff
#

And this doesn’t allocate?

quartz ether
#

no

#

well, format! allocates

#

but you can just use other stuff that doesnt allocate

keen skiff
#

But it doesn’t know hour, minute, second at compile time

quartz ether
#

hm?

#

neither is your code

gentle swift
keen skiff
quartz ether
#

hm?

gentle swift
#

With write!(), you can write some Display thingy into stuff

quartz ether
#

the unsafe doesnt make it know the hour etc at compile time

gentle swift
keen skiff
gentle swift
#

And therefore can limit the string length.

keen skiff
#

Or are you saying it always allocates, in that case I misunderstood.

gentle swift
quartz ether
#

i wonder, does stdout allocates

gentle swift
#

Implementing Display does not tell rust whether you want allocation or not. It tells rust that, if someone gives some way to write some bytes, then you can turn the TimeRepr into a series of byte-writing operations.

#

So, whether it allocates or not depends on which byte-writing operation you provide to it.

#

You can use anything that implements std::fmt::Write as a way to write bytes.

#

And ArrayString implements this Write trait in a way that doesn't allocate.

#

You can use the write!() macro to write stuff that implement Display into anything that implements Write

keen skiff
#

I see, so I might still have to use unsafe if I really want zero allocation, but the responsibility is pushed down the line.

gentle swift
#

You can just use this preexisting crate

keen skiff
#

Will do, thank you

#

I think if anything, it’s much more readable.

spiral cipher
#

Of course you can always make a syscall write to stdout directly and have zero allocations

zinc obsidian
spiral cipher
#

No that's perfectly reasonable

#

It would also fix the soundness issue

pure lichen
#

run cargo-miri to find ub