Ranter
Join devRant
Do all the things like
++ or -- rants, post your own rants, comment on others' rants and build your customized dev avatar
Sign Up
Pipeless API
From the creators of devRant, Pipeless lets you power real-time personalized recommendations and activity feeds using a simple API
Learn More
Comments
-
daniel-wu68311dFrom my understanding / experience, match is a replacement for "switch-case". It is not a replacement for "for/while". Switch doesn't support return either, executing return will exit the whole method / function.
So.. maybe.. rewrite your code? -
gemsy2911dI don't know Rust, but I suppose you shouldn't be able to break out of that case the way you are describing. If you did, the code below the match statement would try to work with response even though its value is undefined, so you always have to return something.
This is unless having undefined variables is common in Rust and the code below would check for that, but in that case, why not do the stuff with Ok(response) directly in the match, as you already have that type check done in that context?
Again, I'm not familiar with Rust, but I hope this makes at least a bit of sense :) -
jestdotty434911dlol what you can't break from a for loop either
it has to specifically be a "loop" only
seriously wat
so now I gotta write my own for loops? -
jestdotty434911d@daniel-wu switch statements are known for having break statement in them. if you don't break with each switch case in most languages you'll keep falling down all the matches (can't remember if it was java and / or JavaScript, but this is also the case in bash and all those era languages)
-
jestdotty434911d@daniel-wu yeah I wrote my code fugly is what I did. now my code is running to the right of my page off my screen and you can't read it whatsoever
early returns make readable code. people will disagree but they are wrong. do believe someone here was making fun of me for liking JavaScript because I didn't mind the cognitive overload of keeping track of what type everything is. well I don't like the cognitive overload of reading x3 as much code than I have to to get to what the code is actually doing -
jestdotty434911d@gemsy match statements are complicated in rust. rust also has, or tries not to have any undefined behaviour to the max degree
and you can't have null in rust. that was their whole innovation
something cool rust does over other languages is I can assign to a variable the result of an if statement for example (and it has no ternaries which is what is typically used in other languages for that), or I can assign the result of a loop block (which I haven't seen before elsewhere), and same with match (and match has far more features than switch statements elsewhere)
so stuff like
let result = loop {
let result = try_something();
if result > 10 {
break result
}
}; -
jestdotty434911dI think the lack of early return from match is a bug though
returning None doesn't capture any variable but in my for loop I was trying to return the variable from the thing I was looping over
... annnd just tried just returning a hard coded value from a for loop and it doesn't like it either. yeah I think this was their intention and they just didn't realize someone would be returning objects without lifetime violations out of these structures -
jestdotty434911dok nvm got it
it's cuz you'd need an else statement if nothing in the loop matched
though with match statements you have a catchall, so not relevant there
I'd just say allow breaks in for loops just the return would always be an Option
oh well
or maybe they did some academic math on it and idk what I'm talking about -
cafecortado760211dBreak inside switch statements are just boilerplate code and a source of bugs when you forget them. I'm glad that new languages don't support them.
-
daniel-wu68311d@cafecortado What you said bro. IMHO switch-case should only contain opening curly brace and closing curly brace. Using colon, line break, and break; statement to conclude it --> feels so archaic and smell of BASIC language. Unfortunately most languages still do this, for backward compatibility. Break; should be something you use to end an encasing prematurely, it shouldn't exist as the last statement on every cases.
-
lorentz1497111dmatch is not switch, the equivalent of a break from switch happens automatically every time. If you assign the match expression to a variable you need to either produce a value of the right type or break out of some block that contains the assignment so that the assignment never happens. This is all completely logical and necessary.
-
lorentz1497111dyou can break out of any loop. you can't break WITH A VALUE from a loop that also has non-break exits such as a for or while, again, this is completely necessary for correctness. The difference between a break with and without value is also emphasized in the error message when you try to break with value from the wrong kind of loop.
-
lorentz1497111dThere are also named blocks that you can target with breaks, this allows you to express any control flow that doesn't try to skip assignments.
-
jestdotty434911d@lorentz NO EARLY RETURN
Ok(match x {
Thing::A => Some(12),
Thing::B(value) => {
let Some(value) = value else {
//????
return None
};
//do a bunch of stuff
Some(value * 36464)
}
})
this is stupid:
match x {
Thing::A => Ok(Some(12)),
Thing::B(value) => {
let Some(value) = value else {
//????
return Ok(None)
};
//do a bunch of stuff
Ok(Some(value * 36464))
}
}) -
12bitfloat895411d@jestdotty No, theres no early "return" from within a match arm
In your specific case you can either solve this with a nested pattern or by mapping the option:
(or traditionally with a mut variable and an if, if youre feeling old school)
// Using nested pattern
Ok(match x {
Thing::A => Some(12),
Thing::B(Some(value)) => {
Some(value * 36464)
}
Thing::B(None) => None,
})
// Mapping the option
Ok(match x {
Thing::A => Some(12),
Thing::B(maybe) => {
maybe.map(|v| v * 36464)
}
}) -
12bitfloat895411d@jestdotty Or if you REALLY want the nested if, you can do it like this:
Ok(match x {
Thing::A => Some(12),
Thing::B(value) => {
if let Some(value) = value {
Some(value * 36464)
} else {
None
}
}
}) -
jestdotty434911d@12bitfloat ragh my example was again more simpler than my code
I'm parsing a bunch of response objects and if one of the nested values can't be parsed I return None, but there's parsing beforehand and parsing after
the last one is how I ended up doing it but I fucking hate it. no early return
considering your level of confidence on the matter I'm gonna go with it's not possible and not in there but maybe they'll add it later -
12bitfloat895411d@jestdotty Not sure how your code looks like but there is probably a better approach than these complex matches
What are you matching anyways? If it's just for "normal" error handling / detecting absent values you can use the try operator, which is a form of early return
By default it's for the entire function call though (returns from the function), else you need to unstable try_blocks feature:
https://play.rust-lang.org//...
Of course when you're dealing with Results its a bit more complex than Option since the error types have to be compatible. The cool thing is, the try/? operator calls .into() on the error to try to make it compatible with the target error type of the function / try block, so if you are frequently turning a ParseError into RequestError, you can just implement From<ParseError> for RequestError instead of having to call .map_err() all over the place -
jestdotty434911d@12bitfloat ye the whole rust error thing I'm not totally on board with. don't know if I like it just doing whatever until I find a way of dealing with them well
ok if you wanna code review I could show i guess -
12bitfloat895410d@jestdotty Something approximately like this should work (haven't tested since I can't compile it): https://play.rust-lang.org//...
This relies heavily on the try operator which is awesome for error handling and the thiserror crate for defining an error enum. (you might also have to turn on the json feature of ureq)
Apart from that the code looks pretty good, maybe some small nitpicks:
* you use a lot of inline defined structs, there is nothing wrong with that but it does harm readibility a bit imo
* I'm not too hot on making Request an enum and having send return an "untyped" Response, I would rather have concrete structs, but it's not that bad -
@12bitfloat I see
I try to avoid using libraries as much as possible, comes from my node.js npm days and gonna be stubborn about it 😝
request enum comment is interesting. do you have an example of that? I do think that can be improved somehow but it didn't occur to me how!
---
I see the convention then is to wrap errors in more error objects. sounds so annoying
why is turning on json feature in ureq favorable? I'm not sure why rust crates keep adding in such things as "features" when you could independently be using them, like plug and play. it seems to couple things too much to me? it ruins clear boundaries between libraries I'd think? -
@jestdotty You don't /have/ to wrap errors into enums, only if you want to programatically handle them. For very simple error handling check out the anyhow crate which gives you an opaque "Error" type which any other error can be converted into and which captures a stack trace, etc. Then you can still use the ? operator but not worry about defining your own error enum
Trying to avoid dependencies makes sense but Rust has a lot of really high quality libraries, so don't feel to bad about using
The feature flag on ureq has actually a good reason: They don't want to force you to also depend on serde_json, so all that code is optional and has to be enabled with the feature flag should you want it (in this case you do to use Response::into_json()) -
@jestdotty For the request enum: It's mostly the idea of making invalid states unrepresentable. In your code, calling send on the request enum gives back potentially any response even though each request has exactly one response "type" it returns.
Now you have to match against that response enum to extract out the actual variant you want, resulting in a runtime panic if there is a bug. Using the type system such a bug could have been caught at compile time
In your case just having three different concrete request and response types with three different send methods is probably the easiest way.
For more complex scenarios you could define a trait with an associated type denoting the response type:
pub trait Request {
type Response;
fn send(self) -> Self::Response;
} -
@12bitfloat ok thanks for the request and response template gonna try that. I do have a list of request objects that all go in somewhere. yeah having cases that might never happen is annoying and I don't like it either
-
@12bitfloat ok the trait request way seems nice conceptually, and actually then the response objects can be the naked types without an enum. wheee
buuut
I can't like save all the requests I want to do to a vector of box dyn traits and save it to disk with serde
it's always something
so guess I'll want to rework that a different way -
@jestdotty You can by making it an enum:
enum AnyRequest {
Balance(BalanceRequest)
Send(SendRequest)
}
This is a pretty common pattern. As with the trait: Don't overcomplicate! Unless you are writing generic functions bound by that trait (<R: Request>) you don't need it. Actually you could also use it to store any request by using trait objects (e.g. a list of them would be Vec<Box<dyn Request>>) but then you can't really use them since methods returning associated types arent object safe -
@12bitfloat ragh this is the weird sorts of things that make it all like an unintuitive straitjacket to me
-
@jestdotty Yeah... inheritance is /really/ not a thing in Rust, which even to me still feels kinda wrong.
Instead of open sets (anything that implements this) you have closed sets (anything I have a declared a variant for)
I still am not totally happy with this, but it's how it is and it works well enough. On the plus side, you generally have way more performant code, even if it's kinda annoying
Though to be honest, using Rust has massively improved my approach to coding. In the inheritance mindset of Java or JavaScript (I know it's prototypes but you get the point), you tend to try to "future proof" way too much, thinking about all the ways you might want to extend it in the future and designing for that.
IMO it's so much better to code for the present, implementing what you actually need for the usecases you actually have. Rust pushes you into that direction which I really like -
@12bitfloat I think I was good at avoiding future-proofing
with rust I rewrite a lot and it's slow. maybe that gets better once my brain gets used to it idk -
@jestdotty You'll get the hang of it after some time. Rust looks deceptively familiar but it /really/ isn't. Writing Rust is a skill all of its own
And yeah, code architecture is probably the hardest thing in Rust. You really need to think about it before hand so that you don't paint yourself into a corner with how your ownership and lifetimes work...
Related Rants
lol rust has no early return from a match statement
continue to skip rest of loop
break to exit a loop
return to exit a function
they put in let Ok(response) = request.send() else { return None };
then you can use response like normal after
but let's say I wanna know what the error was (Ok being a variant of Result::Ok or Result::Err, and the above allowing you to destructure and go on or exit early because can't destructure)
let response = match response.send() {
Ok(response) => response,
Err(err) => {
// log error to file or whatever
eprintln!("{err:#?}");
//????? HOW DO I BREAK OUT OF HERE
return None //whole function shits itself instead of just exiting match
}
}
//does some stuff with response
actually in my case the result will be wrapped in a Ok again so I'm not doing justice to explaining this problem, fux
but basically I need to exit the match without ending the function
come on, match is a loop. let me break, fuckers.
rant
rust
match
break