-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[stdlib] Reintroduce Stringlike
trait and use it for Stringlike.find()
#3861
base: nightly
Are you sure you want to change the base?
[stdlib] Reintroduce Stringlike
trait and use it for Stringlike.find()
#3861
Conversation
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
Hi @JoeLoser and @ConnorGray sorry if I'm drowning you in PRs I took your advice quite literally, they are mostly small and independent. In this one I'm coming across the same problem as in #3677 where I tried to build a generic function that returns a One of the ideas I had was adding a method We could make |
Hey @martinvuyk, thanks for breaking down the PRs, they're looking great, no need to apologize at all😄 With regards to this PR specifically I think I see what you're aiming for in trying to build an abstraction over all "string like" collections ( To illustrate what I mean, let's have a look at the contract of the trait Stringlike(CollectionElement, CollectionElementNew):
alias is_mutable: Bool
alias origin: Origin[is_mutable]
fn byte_length(self) -> Int:
...
fn unsafe_ptr(self) -> UnsafePointer[Byte, is_mutable=is_mutable, origin=origin]:
... If we compare that to struct StringSlice[
mut: Bool,
origin: Origin[mut]
]:
fn byte_length(self) -> Int:
...
fn unsafe_ptr(self) -> UnsafePointer[Byte, mut=mut, origin=origin]:
... we see that they're essentially identical. I think that this means that a function accepting an All else being equal then, I think the simpler design choice to use a concrete type ( For an example, consider this these two different signatures: fn find[T: Stringlike, //](self, substr: T, start: Int = 0) -> Int:
fn find(self, substr: StringSlice, start: Int = 0) -> Int: For each signature, the function body can do essentially the same things: get the byte length, and work with a pointer to the string data. But the I'm curious to hear your thoughts on this as well 🙂 — this is just my first impression after seeing how this abstraction is planned to be used initially, but perhaps there's advantages I've missed, or a future state you're building towards that I might not see yet 😌 |
@ConnorGray yes I like the idea of using This all relates to my goals for abstracting the "12345".as_bytes().find("123".as_bytes()) to become Span[Byte]("12345").find("123") by having the signatures receive In the case of so going back to this specific PR, I'd like to achieve something like trait Stringlike(CollectionElement, CollectionElementNew):
alias mut: Bool
alias origin: Origin[mut]
fn as_string_slice(self) -> StringSlice[origin]:
... What this enables is for example if someone decided to implement a very efficient var custom_value = ASCIIString("123")
var stdlib_str = "123"
print(stdlib_str == custom_value)
print(String(stdlib_str) == custom_value)
print(StringSlice(stdlib_str) == custom_value) without needing to implement the To avoid huge monomorphization steps we could make the implementations of the |
Hi Martin, thanks for elaborating on how you're intending to use this abstraction, that helps a lot in my understanding 🙂 Could we accomplish many of the same goals if we supported implicit conversions from all of the "string-like" types to For instance, the example you provide: "12345".as_bytes().find("123".as_bytes())
Span[Byte]("12345").find("123") could be written as: StringSlice("12345").find("123") If we had a I think we could avoid many methods taking a trait Stringlike:
# TODO: Not sure how to write the `origin` correctly today off the top of my head
fn as_string_slice(self) -> StringSlice:
...
struct StringSlice:
fn __init__[T: Stringlike](out self, stringlike: T):
self = stringlike.as_string_slice() Then, we could write many methods to take a concrete To be clear, re. what you said here:
We would instead have What do you think of that approach? The |
I just wanted to add a quick note for context: Us adding an implicit conversion from fn main() {
let string = String::from("foo");
let s: &str = &string;
} |
This is a good approach IMO. I actually already tried to implement something similar. I implemented the current Problem is that So I guess for now we could just use This is OK for now, but it still constraints the types to be owning (to be able to implement the trait and carry the correct origin), in the case of strings I don't think it's really a problem. But for |
Signed-off-by: martinvuyk <[email protected]>
Signed-off-by: martinvuyk <[email protected]>
trait StringLike(CollectionElement, CollectionElementNew):
"""Trait intended to be used as a generic entrypoint for all String-like
types."""
...
trait StringOwnerLike(StringLike):
fn as_bytes(ref self) -> Span[Byte, __origin_of(self)]:
"""Returns a contiguous slice of the bytes owned by this string.
Returns:
A contiguous slice pointing to the bytes owned by this string.
Notes:
This does not include the trailing null terminator.
"""
...
fn as_string_slice(ref self) -> StringSlice[__origin_of(self)]:
"""Returns a string slice of the data owned by this string.
Returns:
A string slice pointing to the data owned by this string.
"""
...
trait StringSliceLike(StringLike):
alias mut: Bool
"""The mutability of the origin."""
alias origin: Origin[mut]
"""The origin of the data."""
fn as_bytes(self) -> Span[Byte, origin]:
"""Returns a contiguous slice of the bytes.
Returns:
A contiguous slice pointing to the bytes.
Notes:
This does not include the trailing null terminator.
"""
...
fn as_string_slice(self) -> StringSlice[origin]:
"""Returns a string slice of the data.
Returns:
A string slice pointing to the data.
"""
...
trait StringLiteralLike(StringLike):
fn as_bytes(self) -> Span[Byte, StaticConstantOrigin]:
"""Returns a contiguous slice of the bytes.
Returns:
A contiguous slice pointing to the bytes.
Notes:
This does not include the trailing null terminator.
"""
...
fn as_string_slice(self) -> StringSlice[StaticConstantOrigin]:
"""Returns a string slice of the data.
Returns:
A string slice pointing to the data.
"""
... @ConnorGray this isn't necessarily the end version, but this is what I mean by saying that we can't yet generalize the origin that the non-owning types return when calling methods like All 3 of these are types that someone else might implement.
|
Reintroduce
Stringlike
trait and use it forStringlike.find()
This allows some ergonomic code and not needing to allocate for many cases like
String("123").find("1")
which would previously cast"1"
to aString
.