mirror of
https://github.com/ramsayleung/rspotify.git
synced 2026-04-27 00:15:56 +03:00
[GH-ISSUE #203] Endpoints with lists of Ids don't accept multiple of them #64
Labels
No labels
Stale
bug
discussion
enhancement
good first issue
good first issue
help wanted
pull-request
question
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
starred/rspotify#64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @marioortizmanero on GitHub (Apr 20, 2021).
Original GitHub issue: https://github.com/ramsayleung/rspotify/issues/203
Describe the bug
I just realized that Ids don't really work in lists. We'd have to use
Box, I think, to support for example items of both episodes and tracks. Otherwise, with a snippet like this:You get:
This is also relatively dangerous because if the user tries to initialize the Ids with
Id::from_uriinstead of the specificEpisodeId:.from_uriorTrackId::from_uri, they will all be converted to the type of the first one in the list and fail at theunwrap.@marioortizmanero commented on GitHub (Jul 14, 2021):
The design for IDs is a bit wrong now that I've tried to actually use it... The thing is that you don't always know at compile-time what kind of ID is being used. So for example, this is impossible to do right now:
Because the arms of the
matchstatement have different types.Any opinions, @kstep ? Sorry for bringing this up so late :(
@marioortizmanero commented on GitHub (Aug 24, 2021):
Here's my reasoning behind all of this, step by step so that anyone else can understand it as well. If I'm wrong at any point please let me know!
So we currently have an
Idstruct that is generic over its type,Id<T>. And for functions likestart_uris_playback, we needTto take different values in aVec/equivalent (as a simplification over theimpl IntoIterator<...>thing). We also want to be able to haveTbe checked at runtime.The first idea that comes to my mind is dynamic dispatching;
Id<dyn T>might work.Ìd<dyn T>to work, whereT: IdType:IdTypemust be object safe, so thatdyn IdTypeis possibleIdshould be defined withpub struct Id<T: IdType + ?Sized>, so that it may take adyn IdTypedirectly (just like howBoxworks). Otherwise we'd have to useId<Box<dyn IdType>>, which wouldn't work either becauseBox<dyn IdType>doesn't implementIdType.Box<T>is. ButBox<T>performs a memory allocation, soId<T>wouldn't be a zero-cost abstraction at all.IdTypesimply containsconst TYPE: Type;in its definition. Which meansIdTypeis not object-safe at all. We would have to makeIdTypea trait that initializesTand then callsget_type(&self) -> Type, which would be type-safe.Id<T>.Another solution would be what I've already implemented in #241. Simply add an
Unknownvariant that doesn't check types at all. You can then haveVec<Id<Unknown>>, which would allow any kind of ID to be passed as its generic parameter. Though this sounds like a cool solution for being able to have IDs defined at runtime (Id<Unknown>), with it you can't enforce the type safetyId<T>is supposedly useful for over a&str.While this is my favorite solution so far, I think there must be something else that's better.
Based on the last solution, we could turn traits like
PlayableIdTypeinto types (sayPlayable) just likeArtistorTrack. One could then convertId<Track>intoId<Playable>withid.into()in order to pass it as a parameter. That way no generics are involved at all overT; it's always going to be a known type.The main problem of this solution is that there is some data loss as well. For example the
Displayimplementation ofId<T>usesT::TYPEto print its URI. What exactly would happen withId<Playable>? It would print the URIspotify:playable:xxxx, which is not a valid URI.A different approach would be to scratch
Id<T>and just useArtistIdand similars directly. What I mean is that we wouldn't have a single type for IDs calledId<T>that implements its logic. We could makeArtistIdand similars, and implement the trait the same way clients work. The main component would be a trait that implements all the logic, sayId, and a struct for each type (ArtistId, etc) that implements the trait. With this one could be generic overIddirectly, haveBox<dyn Id> for runtime usage, andVec<Box>` for multiple values, which is exactly what we need.The main problems are:
Id::from_urican't work with this solution;ArtistId::from_uriwould have to be used explicitly.Idalways available when using IDs; we'd have to add it to the prelude. Considering the user needs to import the prelude to use the clients anyway I don't think it's a big dealSo this solution is basically the same as how clients work. We have a big trait that does everything, and we let Rust "clone" the code for each of our implementations. It's also basically the newtype pattern, but with a common trait for all the newtypes.
Sorry for the long text lol. Just brainstorming. I'll try to implement my favorite solutions in PRs and showcase how they work.
@marioortizmanero commented on GitHub (Aug 24, 2021):
Ugh. Unfortunately literally none of these ideas work. This is what happens when trying to implement
Fromto convert anyId<T>intoId<Unknown>:So in that case the user would have to do:
Which is way uglier and has more overhead than:
@ramsayleung commented on GitHub (Aug 25, 2021):
It turns out that the zero cost abstractions isn't that easy to reach out, it comes with unexpected obstacles :)
@marioortizmanero commented on GitHub (Aug 25, 2021):
Yeah, it's very hard to mix both compile time and runtime. I'm trying a new approach that consists of mixing two of the ideas I explained here. I'll try to have multiple types for
Idinstead of a singleId<T>so that there's no generics instead, AND I'll addUnknownId/similars as IDs as well. The difference is that since theIdtypes are different, it is possible to implement conversions from them (instead of usingdyn, which is impossible given theIdtrait).I'll explain more if I get it right, though it still has a couple drawbacks.