kinda weird constructing the serialization/deserialization for #activityPub types in #rust because it does mean I'm doing a lot of curl requests against my own server (and those of other people) to see what the raw data looks like in the wild
-
jonny (good kind)replied to d@nny "disc@" mc² last edited by
@hipsterelectron
@aud
Nice ok that is helpful.I figure this can be done with macros, and I also am assuming someone has already done it, but it would be awesome to be able to do stuff like
struct MyStruct {
pattern_field: Pattern<"whatever.regex">
enum_field: Enum<"one", "two">
}
and then have the actual code to make that work get generated by the macro. Like for situations when you're parsing external input and don't expect it to already be in the enum type, or it's a single use thing and you wont need to refer to it again.
Again I assume someone has done this, but like for the case asta is talking about here with the super heterogeneous, incomplete, and etc. AP objects, some modeling shorthands would be lovely
-
@aud
This is sick as hell thank you thank you THANK you! I am bookmarking this bc I need to be working on a Halloween costume, but u are tempting me to not -
@[email protected] I'm also using
APValue
, which is literally almost just exactlyValue
from theserde
crate except I didn't want to writeFrom<serde_json::Value>
functions for my AP types because I only want to try and construct AP types under specific circumstances. Plus that allows me to implement AP specific functionality on the JSON. (I have aFrom<serde_json::Value> for APValue
function that does a clean conversion). In fact, I DO have aimpl APValue
block that does a little matching on the context (and will probably do more later): https://codeberg.org/Astatide/satyr/src/commit/eac98f43ea5b3baa170e5c1bbc94f0509716293e/src/primitives/activity_pub/parser.rs#L293 -
Asta [AMP]replied to jonny (good kind) last edited by
@[email protected] no, thank you! I'm glad it's helping to at least inspire!!
I actually am thinking of breaking it out into its own crate? Because I think I want to use it for something else, too, since there's nothing limiting AP as a social media FP protocol.
There's other crates, but they assume you're using it for social media (the lemmy one has it baked in with axum). This would only need to rely on like,serde
,serde_json
, maybe a few other commonly used things. -
d@nny "disc@" mc²replied to jonny (good kind) last edited by
@jonny @aud i really really liked @flaviusb's approach to metaprogramming here https://mastodon.social/@flaviusb/113117546857169170—proc macros generally let you output arbitrary token streams but then you have to output arbitrary token streams
-
d@nny "disc@" mc²replied to d@nny "disc@" mc² last edited by
@jonny @aud rust is generally very very very big on being "explicit" about "tradeoffs" and unfortunately what that means is lots of boilerplate because people feel that's "safer" when that's not what a lot of uses of rust need. it's a very corporate framing that i feel makes the language worse for hacking
-
@[email protected] the
_extends
andObjectFields
trait work for things with multiple inheritance levels, too. I'll be doing the same thing forLinks
, too. -
Asta [AMP]replied to d@nny "disc@" mc² last edited by
@[email protected] @[email protected] yeah, I've had a few partial reworks of what I've been writing (you can see their ghosts in the form of commented out blocks)
But even with something that I think minimizes boilerplate, my object.rs file is almost 1000 lines long and I have derive macros for very boilerplate stuff. -
d@nny "disc@" mc²replied to Asta [AMP] last edited by
@aud @jonny i generally do a lot of the kind of metaprogramming jonny is trying to do and i do it through trait logic bc that makes a nice API but i have banged my head against the wall for many many hours to understand how to do that and how to do that nicely and extensibly (i guess extensibility might be my bias too) but traits just let you express composition they don't avoid boilerplate in fact they require boilerplate to impl. dyn traits create c++-like vtables and can let you achieve some dynamism but not metaprogramming. i obv like rust but i feel what jonny is trying to do is underserved and could be done better (i don't like saying "it's the wrong language" bc that is an excuse for mediocrity imo)
-
d@nny "disc@" mc²replied to d@nny "disc@" mc² last edited by
-
Asta [AMP]replied to d@nny "disc@" mc² last edited by
@[email protected] @[email protected] I mean! You know, it's probably the most raw Rust I've written, period. I'm hardly an expert.
And technically I have yet to confirm that this is a 'good solution' for data in the wild; hence why I'm working with partial serialization/deserialization of wild data to see if things match up the way it should. I do think it will, as it should just basically hit atodo!()
and error out or just useNone
so while I'm testing against raw data, I can update as necessary to either make it handle anything invalid... or specific weird use cases.
I'm trying to keep it as close to the spec as possible while maintaining strong typing and compatibility. Since this is also my way of gaining Rust experience and learning ActivityPub, it felt like a good way to go about it. -
Asta [AMP]replied to d@nny "disc@" mc² last edited by
@[email protected] @[email protected] I like extensibility, too. I'm not a strict object-oriented-kitten, but dyn traits are definitely not a 1 to 1 transfer of the concept and if you have to implement a spec that inherently uses a lot of object oriented ideas... well, I've thought about switching to more dyn traits. I suspect that's how the other crates may or may not implement them (I understand more of why the lemmy activitypub implements Object as a trait), but when you don't actually know the spec (hi)... I wasn't totally sure what I did or did not need on any object, basically.
Plus I figured translating a more OO schema into Rust would help me understand the language more. -
@[email protected] @[email protected] Plus, I don't think just using traits would allow me to implement
To/From
which I feel like allows me more transparent strong typing.
I feel like that approach would result in a lot of data getting thrown away, but to be fair, I expect that a lot of clients/servers throw away huge chunks of the incoming data anyway (for instance, my server spits out_misskey_summary
as part of a note, and obviously Mastodon isn't gonna give a shit about that). But especially if I'm using it for multiple projects, which I would like to, this means I keep all the incoming data in one form (rather than... I assumeserde
just silently dumps data if there's extra payload data that isn't in the struct? I dunno) or another and implementations can choose to extend that by sending/receiving expected data in the_extra
attribute. Or just by derivingObjectFields
-
@[email protected] @[email protected] A lot of this is conjecture on my part, for the record.
Speaking of conjecture, with the minor exception of me shoehorning inheritance in here, I feel like wrapping up different types in enums is very Rust-like... -
Asta [AMP]replied to jonny (good kind) last edited by
@[email protected] @[email protected] I think you could do this with a procedural derive macro. If the struct you run the macro on is self-contained in that it has all the information you'd need to construct your new types, then absolutely.
I feel like all the examples given of proc_macro_derive are actually... really kind of useless because they take in the AST and then "leave it as an exercise to the reader to actually use the fucking AST in any way".
I have a (convoluted and old) example here, where I basically create SI prefixed variants of field-less structs and create a set of operator overloads to allow you to do order of magnitude correct math on them (even including machine precision; if they're too far apart it won't create that addition/subtraction/etc operator overload)
https://github.com/astatide/decay/blob/dbe1dd32af27072966140f92fb0bf6645626d140/crates/decay_si_derive/src/lib.rs#L72
it's a little nasty but so long as you define your struct in a very specific way I think you can create whatever you want.
In this case I just create a new string and just start appending lines of code to it. It creates a whole new set of structs and operators.
You can see the test/result here: https://github.com/astatide/decay/blob/dbe1dd32af27072966140f92fb0bf6645626d140/crates/decay_si/src/lib.rs#L23
I kinda hate it and love it -
Asta [AMP]replied to Asta [AMP] last edited by [email protected]
@[email protected] @[email protected] (it's super not rust like but I don't really care as I feel just because it's not 'standard' doesn't mean it's unsafe w/ regards to types). It could be a bit of a footgun, admittedly.
let kM = KiloMeter; let mut d = kM(1.0); // 1.0 d += 1.0; // 2.0 assert_eq!(*d, 2.0); d += Meter(1.0); // does not convert to a meter now yay. 2.001! d += 1.0; // Now 3.001
-
Asta [AMP]replied to Asta [AMP] last edited by [email protected]
@[email protected] @[email protected] fuuuuuuck I still love operator overloads
I used them once in a genetic algorithm to do complex handling of data in a trivial, logical way. I fucking love group theory and I feel like people in comp sci tend to not for whatever reason.
If I want to "add" two apples and 3 bananas together I sure as fuck should be allowed to because "add" is an arbitrary operation over a group of "fruits" and if I want that to return a type "basket of fruits" by god who the fuck are you to stop me.