Hello,
It looks like Rust forgot to implement the ternary conditional operator
It's operator that looks like this: condition ? trueExpression : falseExpression
This important feature lets you do things like return value == 5 ? success : failure;
Without this Ternary Conditional Operator you must use if statements which make ugly duplicated code (leads to bugs)
I think it easy to add support for this in Rust
Rust already has this: return if value == 5 { success } else { failure }.
@dscorbett That does not look very nice
I have been very happy with our if-expression syntax. Not everyone loves the ternary-?:
-1.
(Downgrading so-called "importance")
I continue to be against ever adding the ternary.
:-1: We have if ... else, which does the same thing and is more readable IMO
Sorry, but :-1: from me too. I sympathise with the argument for terseness, but would rather we not duplicate the functionality of if/else.
Rust tries to avoid having multiple ways to do the same thing. Furthermore, the : symbol is already more overloaded than I would like, and classic ternary syntax would be grammatically ambiguous with the already-accepted type ascription RFC. Furtherfurthermore, there are vague plans to use the ? symbol for language-level error-handling support in the future.
We added ternary at one point strictly to satisfy this aesthetic preference, and then later removed it due to deciding it didn't carry its weight.
Following the famous "time traveller rule of 3", one should only backtrack on such a decision at most twice; three times risks the integrity of spacetime.
:-1:
I think if..else is more readable than dedicated ternary symbols
:-1:
EDIT: Sorry, I don't usually like being so terse, so let me just expand a touch. We have if/else, which is perfectly readable, and ternary just doesn't come up that often that it merits two similar bits of syntax. Also, I find very frequently I wind up using match or if let instead of a simple boolean expression, and I like the fact that which ever form of "ternary-like" thing I am using, it looks roughly the same. Basically, not needing a ternary expression is one of the great things about having an expression-oriented language!
I am grateful that this was removed, so :-1:
There's a lot of subjectivity in whether the ternary operator is a good idea or not; But not everyone has to want a feature for it to be worth including. C, C++, and a bunch of other languages have this generally useful feature.
The syntax is also similar to the match we already have:
match token {
'+' => accumulator += 1,
'-' => accumulator -= 1,
_ => { /* ignore everything else */ }
}
Which is of the form condition => expression if true, stuff to do if false
return foo == 1 ? 0 : 1;
is also in the form condition ? expression if true : expression if false;
Having the ternary operator allows the programmer the choice to use verbose ifs and elses or ?: which in my opinion if a good choice to leave open.
With that being said, we probably shouldn't use the same syntax as C given the use of ':' to indicate types and with associated functions, we could instead (as a suggestion) have the syntax
return foo == 1 => 0, 1;
Having the ternary operator allows the programmer the choice to use verbose ifs and elses or ?: which in my opinion if a good choice to leave open.
I disagree. Because that now means others will have to read that code. Sometimes fewer choices are better than more choices.
I'm with most others: big :-1: on this.
Systems programmer _should_ be very familiar with ?; give we've been using C and it's derivatives since the early 1970s. And you can't make the argument that it's confusing for beginners because Rust has other completely foreign notions like lifetimes, closures and patterns. So the problem is really just personal preference, and yes you can choose to never use the ternary operator but to be honest I was surprised it wasn't already in the language, coming from a C background.
Rust already has a lot of choices; you can specify types, or let the compiler infer them.
The ternary operator is also not the same thing as if (...) { thing } else { thing }, you can't (at least in most implementations) have more than one expression as 'thing' in a condition ? thing : thing;
Apparently you can, in some cases. Woops and thanks @arielb1
_Sometimes_ fewer choices are better than more choices. I believe it to be true in this case. That Rust sometimes gives choices is not an argument in favor of always having more choices.
I don't mind admitting it's personal preference. I've read enough ternary spaghetti in my time to know it's not the kind of spaghetti I want to consume.
@argusp
You certainly can have multiple expressions.
int main() {
int x;
return 1>0 ? ({ x=1; x; }): 2;
}
(That's a GNU extension.)
@BurntSushi I absolutely understand where you're coming from, I'd gladly throw the K&R book at anyone who nested the ternary operator or used it for anything that warranted a verbose if else statement. But still, some of us want it so why not?
It is not particularly sustainable to support the union of all functionality that anyone has ever wanted.
I don't think any new syntax should be added under the justification of "why not."
Too me this is really noise. Rust's "everything is an expression" provides a really clean way of doing this namely if cond { a } else { b }.
To me, the argument for it is the same as the argument for match - It's not necessary to have match for the language to be able to work, a match is a glorified/crazy if/else structure, but it looks nicer and is more understandable & concise, so it's in the language and everyone's happy with it. The ternary operator is the flip side of this, yes it is equivalent to some if/else based structure, usually a very simple one, but it adds code clarity if you can dispense with a large amount of syntax in favour of something much less messy.
/* Preferable, in my opinion, has just 2 characters of syntax */
let x = y > 3 ? 3 : 0;
/* messier imho, 10 characters of syntax*/
let x = if y > 3 { 3 } else { 0 };
To me the justification for this is the same as the justification for the following:
/* I'm sure we can all agree the match version here is better */
let x = match token {
'+' => 4,
'-' => 3,
'*' => 2,
'/' => 1,
_ => 0
};
let x = if token == '+' {
4
} else if token == '-' {
3
} else if token == '*' {
2
} else if token == '/' {
1
} else {
0
};
In fact with the ternary operator we can have nice* things like this concise case-like statement:
let x = y < 3 ? 3 :
y < 10 ? 10 :
y < 23 ? 11 :
y < 42 ? 0 ;
(* Nice, in some people's opinion, and probably awful in other's.)
:-1:
if ... else is more readable, and just looks nicer IMO. I don't agree that terser syntax implies better syntax in all cases.
Incidentally, note that match can do things an if ... else chain can't do, or cannot do easily:
fn main() {
let mut my_option = Some(5);
match my_option {
// `ref mut x` creates a binding `x` which is a mutable
// reference to the number inside `my_option`
Some(ref mut x) => { *x = 10; },
None => { }
}
// `my_option` is now `Some(10)`
println!("{:?}", my_option);
}
So, I don't think the relationship between match and if ... else, and between if ... else and ?:, are very similar.
@AngusP
It's not necessary to have match for the language to be able to work, a match is a glorified/crazy if/else structure,
I'm afraid you do not understand match. It is strictly _more_ expressive than if / else.
@pnkfelix sorry yeah, I should have worded that better, I'm aware that pattern matching is strictly more expressive than ifs & else's, and that in rust if/else is treated as a special case of match.
If/else and match both exist because they aren't the same thing exactly, while ?: and if/else are the same thing. That however does not mean we should immediately dismiss it's value. Bearing in mind these are language choices not style choices we're trying to make.
Something interesting; due to #803 and its expr: Type syntax being accepted, this may be in conflict with #243 (not yet accepted) due to it using expr? syntax as sugar for try!(expr).
fn bar(_x: bool) -> bool {
false
}
fn foo() -> Result<fn() -> bool, bool> {
let f = Ok(bar);
let bool = true;
// Interpretation 1: Err(try!(f)(bool): bool)
// Interpretation 2: Err(if f { bool } else { bool })
Err(f?(bool):bool)
}
fn main() {
println!("{:?}", foo())
}
Now, it isn't completely ambiguous in this case (f isn't a bool after all, and thus interpretation 2 fails to compile), but the fact that it comes down to the type of f makes me wary.
@AngusP the argument you're looking for is not match but if let, which is purely syntactic sugar for a special case of match. But of course this is not a compelling argument in itself, just because the language has some sugar doesn't mean it has to adopt all sugars.
Some of the questions to evaluate regarding a proposed sugar are:
I think that the reason a ternary particularly doesn't hold up to me is that while if cond { A } else { B } is more characters than cond ? A : B, it isn't any more boilerplate in terms of control paths represented. Compare that to the savings in if let PAT = EXPR { A } versus match EXPR { PAT => A, _ => () }, which has a whole unnecessary match arm.
I also don't think its actually that common of a pattern, because if itself is a less common pattern in Rust. I just grepped an ~5000 line codebase I'm working on for ifs appearing in the rvalue position, and found only 1, whereas I found 14 match statements in that position (there were 88 ifs and 96 matches overall).
Another problem getting this through I think is that the reason ternary exists in many languages is that they do not have if expressions. Not having a ternary serves as a distinction and a pedagogical function about Rust's expression-orientedness.
And finally there are the syntactic issues with using those symbols for ternary, which would necessitate some alternate proposal like => ,. So it wouldn't be any more familiar to people coming from other languages, but instead of introducing expression-orientedness it would just be a weird thing about Rust's ternary operator.
In fact with the ternary operator we can have nice* things like this concise case-like statement:
let x = y < 3 ? 3 : y < 10 ? 10 : y < 23 ? 11 : y < 42 ? 0 ;
It's not a good argument if your "nice" example doesn't compile. I assume you wanted to return something else in case y > 42 ? let's use 42
The rust version is much more clear imo, and it's visually harder to mess up match statements.
let x = match 4 {
0...3 => 3,
0...10 => 10,
0...23 => 11,
0...42 => 0,
_ => 42,
};
IIRC we are getting ...N patterns, so the zero can go away, covering negative values, too.
We explicitly removed the ternary long ago, due to the duplication with if. An RFC to add it back would have to come up with a significant justification, to override that big negative.
As such, I'm gonna give this one a close.
I made a different point about contrast given a language with block indenting instead of brace-delimited blocks.
How would a sans-brace syntax handle nested if-else, or multiple lines within what would have formerly been a brace-delimited block?
How would a sans-brace syntax handle nested if-else, or multiple lines within what would have formerly been a brace-delimited block?
See how Python and Haskell handle it with indenting of the block.
Note in my idea, it is still possible to indent to continue an expression on the next line, without creating a nested block; and this is accomplished by indenting more than the exact number of (e.g. 2) spaces required to created a nested block. Also note that outdenting by exactly that same number (e.g. 2) spaces is required to end the block.
I concluded that for Rust's brace-delimited design, the absence of a ternary operator is the optimum design decision. For a block indenting language, the optimum appears to be to require : instead of else only in the single-line case and only allow it where it is in an expression that is assigned.
I'm very new to Rust, but I'm not new to programming. I believe it's not really necessary to have multiple ways of doing the same thing! specially when a clean readable version exits, in this case if-else.
My thoughts on this are that we don't need to make Rust any more terse than it already is :smile:
I, as a Rust noob also agree. I was a bit surprised when my ternary using code didn't work, but I'm actually glad I won't be tempted to use it.
I see this is a pretty loaded request. I prefer ternary conditionals for chain loading case assignments. The syntax is perfectly readable once your taught the language structure. Personally if a langue doesn't support ternary it should at least support a postfix if.
if I can't have
return (value == 5) ? success : failure;
I would prefer
return success if (value == 5) else failure;
then
return if value == 5 { success } else { failure }.
I had no idea that ternary operator was such a divisive topic... I am new to Rust, however, I find the arguments against ternary operator unrefined. The argument for not having ternary operator based on the clarity and existing functionality of the following :
let x = if (true) { 1i32 } else {2i32}
is not that great of an argument to make. As it is easily understood in the form of
let x = true ? 1i32 : 2i32
or perhaps in the form similar to match?
let x = true => 1i32, 2i32
I think the discourse here is better served by determining what type of future Rust should have, whether a constrained Java like syntax or a free C/C++ syntax. Both of have merits and pitfalls. If Rust should be an expressive language for the developer, then implement ternary operator. If Rust should be a controlled language, then do not. Readability, in my opinion is a terrible argument... as I am sure I can make a terrible if else statement that would make you want to gouge your eyes out.
Maybe something like this is possible?
let x = if (true) { if (false) { true } else { false} } else { if (true) { false} else {true} }
@nyanzebra in Rust you can say if true { 1i32 } else { 2i32 }, so you save another pair of parens. Also, since we don't want any dangling-elseish code we'd need {} around the expressions and then we are back at rust's if/else notation.
Maybe something like this is possible?
In ternery notation that would be
let x = true ? false ? true : false : true ? false : true;
Which in my opinion is way worse than the if/else version
Also it should look like
let x = if true { if false { true } else { false } } else { if true { false } else { true } };
Which I'm fairly sure that clippy will tell you to turn into
let x = if true { !false } else { !true };
And then after another round of clippy:
let x = if true { true } else { false };
And after a third round
let x = true;
if you want to address multiple booleans you can always use a match:
match (a, b) {
(true, true) => ...,
(true, false) => ...,
(false, true) => ...,
(false, false) => ...,
}
Readability, in my opinion is a terrible argument...
The thing is. That horrendous if/else you showed, is be formatted by rustfmt to
let x = if (true) {
if (false) { true } else { false }
} else {
if (true) { false } else { true }
};
Which is cleanly readable. I'm not even sure how to format the ternary operator in a readable way
/rant /sorry
5 minutes into rust first thing that i look up was Ternary operator. Im back to C. RIP.
You need to step up your trolling game significantly if you want to fence with the rust community. We've been hardened by the borrow checker, so these puny attempts just get tagged A-musing + wontfix
No interest in trolling or community . Thank you. Bye.
if you're choosing your language based on the ternary operator I think you may have somewhat larger issues :)
"back to C" he said, loading his gun for another round of Russian roulette...
Really? Enough.
Most helpful comment
We added ternary at one point strictly to satisfy this aesthetic preference, and then later removed it due to deciding it didn't carry its weight.
Following the famous "time traveller rule of 3", one should only backtrack on such a decision at most twice; three times risks the integrity of spacetime.