How to return a Span that refers to a struct member from a trait method?

I would like to do the equivalent of this rust code:

// Note: lifetimes added for explicitness. These would normally be elided. 
trait Searchable: Clone {
    fn buffer_to_search<'a>(&'a self) -> &'a [u8];
}

#[derive(Clone)]
struct Record {
    name: Vec<u8>,
    seq: Vec<u8>,
}

impl Searchable for Record {
    fn buffer_to_search<'a>(&'a self) -> &'a [u8] {
        &self.seq
    }
}

fn main() {
    println!("Hello, world!");
}

Specifically, return a reference to a slice of bytes on the member struct. I’ve found two ways to do this, but neither feel right.

trait Searchable(Copyable, Movable):
    fn buffer_to_search(ref self) -> Span[UInt8, __origin_of(self)]:
        ...


@value
struct PointerRecord(Searchable):
    var name: List[UInt8]
    var seq: List[UInt8]

    fn buffer_to_search(ref self) -> Span[UInt8, __origin_of(self)]:
        return Span[UInt8, __origin_of(self)](
            ptr=self.seq.unsafe_ptr(), length=len(self.seq)
        )


@value
struct RebindRecord(Searchable):
    var name: List[UInt8]
    var seq: List[UInt8]

    fn buffer_to_search(ref self) -> Span[UInt8, __origin_of(self)]:
        return rebind[Span[UInt8, __origin_of(self)]](Span(self.seq))


def main():
    pass

@owenhilyard suggested that buffer_to_search as defined on the trait could be generic over the origin, but I have not been able to sort out a working version of that, or find a good example in the stdlib.

Is there an explicit way to construct this without rebind or going through the span pointer constructor?

trait Searchable(Copyable, Movable):
    fn buffer_to_search[mut: Bool, //, origin: Origin[mut]](ref [origin] self) -> Span[UInt8, origin]:
        ...


@value
struct PointerRecord(Searchable):
    var name: List[UInt8]
    var seq: List[UInt8]

    fn buffer_to_search[mut: Bool, //, origin: Origin[mut]](ref [origin] self) -> Span[UInt8, origin]:
        return Span[UInt8, origin](
            ptr=self.seq.unsafe_ptr(), length=len(self.seq)
        )


def main():
    var record = PointerRecord(name = List("name".as_bytes()), seq=List("seq".as_bytes()))
    
    for byte in record.buffer_to_search():
        print(byte[])

I don’t think there’s a way to avoid the pointer constructor.

1 Like

Thanks @owenhilyard!

Yeah, I don’t love going thought he pointer constructor, but thank you for confirming I’m not missing a better way to do this.

Your solution was already generic

AFAIK these two are synonyms, ref is the non parametetrized way to express the same

Level of abstraction 1: No mut boolean required

fn buffer_to_search[O: Origin](ref [O] self) -> Span[UInt8, O]: ...

taking that up a notch

fn buffer_to_search(ref [_] self) -> Span[UInt8, __origin_of(self)]: ...

and using plain ref is equivalent to ref [_]

And I personally prefer rebind since it is free. constructing an instance is not. The only thing “unsafe” about rebind is that you need to be sure that you aren’t rebinding things that aren’t true. Casting the origin of a struct’s internal data binding it to the struct’s origin (which is what you are doing) is fine.

So all in all I’d do:

    fn buffer_to_search(ref self, out result: Span[UInt8, __origin_of(self)]):
        result = rebind[__type_of(result)](Span(self.seq))
1 Like

Thank you for the breakdown, that’s helpful to see.

Is this something that is considered a language defect at the moment that may get a fix, or is this origins working as intended?

The big problem is traits and trying to be generic over origins. The “correct” way to do the function would be to link it to the struct’s variable (not sure if this works though):

    fn buffer_to_search(ref self) -> Span[Byte, __origin_of(self.seq)]:
        return Span(self.seq)

but your trait would no longer apply to the struct. This is a problem I’ve had when trying to do things generic over types that return a Span (like String and StringSlice) in the stdlib.

Is this something that is considered a language defect at the moment that may get a fix, or is this origins working as intended?

I think this is a current limitation in the language, and that the solution will come naturally once we have parametrizable traits

1 Like

This topic was automatically closed 7 days after the last reply. New replies are no longer allowed.