The Crystal Programming Language Forum

Slice from UInt8

Can I get a Slice pointing at a single UInt8 without creating a new Bytes or StaticArray?

Bytes is defined as just alias Bytes = Slice(UInt8). With this in mind your question could be rephrased to “Can I get a Slice … without creating a new Slice …?” :)

So the answer is obviously no, just do Bytes[10u8].

However maybe for your usecase you can avoid allocating the slice by not using a slice altogether? Always good to mention the underlying problem for which you seek a solution, rather than how to realize a solution you already came up with :)

This might be what you want:

x = 0u8
slice = pointerof(x).to_slice(1)
slice[0] = 1u8
p slice # => Bytes[1]

It doesn’t allocate anything on the heap.

1 Like

I really want to avoid heap allocation and Bytes.new.

Bytes[] calls T.slice

  # The slice is allocated on the heap.
  macro slice(*nums, read_only = false)

Well you got your solution above :) Maybe there’s a safe and more idiomatic one though, one that doesn’t even need a slice…

How would you handle passing portions of c structs to IO methods, Digest#update and other stdlib methods that only take a Slice?

For IO there’s the write_bytes interface which is supported by all primitive types in stdlib, through the to_io interface defined on them. One could expand on that by defining to_io methods for compound types.

If something is type restricted to a Slice argument with no alternative API, of course you need to get your hands onto a slice somehow :)

In general I still think providing the context on where you need something is still beneficial to the question quality and allows to offer solutions outside of the path you might have imagined so far. That’s all I was hinting at :)

1 Like

@Didactic.Drunk can you provide some sample code of what you are trying to do?

@asterite Porting existing code from ruby/c.
@exilor’s answer seems to work but I haven’t validated the hash values yet.

slice = pointerof(struct.member).to_slice(1)
digest.update slice

Memory allocations using Bytes.new(1) were killing performance. I could keep a persistent Bytes allocated but a Slice directly to the UInt8 seems like a better option.

Using pointerof is fine as long as you don’t give that pointer to something that keeps a reference to it.

Also see this: https://github.com/crystal-lang/crystal/blob/2cbe65b0a13c8a2ce91bd4799f5b96b8e1953097/src/io.cr#L842-L845

  def write_byte(byte : UInt8)
    x = byte
    write Slice.new(pointerof(x), 1)
  end
1 Like

I’d also point out that StaticArray doesn’t allocate on the heap, so it might actually be a viable, more high-level solution where you don’t need to work with pointers directly.

1 Like

In the process of making a PR adding write(byte : UInt8) to Digest I wondered if it made more sense to add to_slice to UInt8?

Thoughts?

Isn’t that what DigestIO is for? So it gets methods like IO#write_byte inherited for free?

I don’t think so. DigestIO calculates a Digest of the unmodified stream as you read/write an IO. I don’t have an IO and if it was it’s not the entire stream or in order.

I wondered if it made more sense to add to_slice to UInt8 ?

The comment above is about adding easy UInt8 compatibility for every function that accepts Bytes which is more than IO.

I think it’s fair if doing this is a bit hard, IO#write_byte(s) is just so much more sane of an interface and we should push people towards it where possible. I don’t want people to end up with io.write 1u8.to_slice.

I think write takes anything that responds to #to_slice as does Digest#update and many more.

What I propose:

io.write 1_u8
digest.update 2_u8
cipher.update 3_u8
any_duck_typed_to_slice_accepting_method 4_u8

If it’s not done at the UInt8 level, code must be repeated for every function that wants to accept a Slice | Byte

Maybe DigestIO is a misnomer and Should be DigestIOWrapper or something less terrible, and then Digest should just include IO and disable read?

We have this wonderful to_io interface, it would indeed be a shame to not use it.

Maybe Digest should even use a builder API akin to String.build, yielding an IO. Having to call final on it always felt like an odd API to me.

There’s write_byte for IO. I think having it for Digest is good too.

UInt8#to_slice will never happen because it’s unsafe. If you retain the slice and pass it around, it might crash your program or cause undefined behavior.