Skip to content
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

Rust Needs The Ternary Conditional Operator (-?-:-) #1362

Closed
dangerrust opened this issue Nov 10, 2015 · 48 comments
Closed

Rust Needs The Ternary Conditional Operator (-?-:-) #1362

dangerrust opened this issue Nov 10, 2015 · 48 comments
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.

Comments

@dangerrust
Copy link

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

@dscorbett
Copy link

Rust already has this: return if value == 5 { success } else { failure }.

@dangerrust
Copy link
Author

@dscorbett That does not look very nice

@pnkfelix
Copy link
Member

I have been very happy with our if-expression syntax. Not everyone loves the ternary-?:

@nagisa
Copy link
Member

nagisa commented Nov 10, 2015

-1.

@pnkfelix
Copy link
Member

(Downgrading so-called "importance")

@Gankra
Copy link
Contributor

Gankra commented Nov 10, 2015

I continue to be against ever adding the ternary.

@pnkfelix pnkfelix changed the title Important: Rust Needs The Ternary Conditional Operator Rust Needs The Ternary Conditional Operator (-?-:-) Nov 10, 2015
@jonas-schievink
Copy link
Contributor

👎 We have if ... else, which does the same thing and is more readable IMO

@brendanzab
Copy link
Member

Sorry, but 👎 from me too. I sympathise with the argument for terseness, but would rather we not duplicate the functionality of if/else.

@bstrie
Copy link
Contributor

bstrie commented Nov 11, 2015

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.

@graydon
Copy link

graydon commented Nov 12, 2015

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.

@frewsxcv
Copy link
Member

👎

I think if..else is more readable than dedicated ternary symbols

@nikomatsakis
Copy link
Contributor

👎

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!

@pnkfelix
Copy link
Member

see also rust-lang/rust#1698 rust-lang/rust#1705

@tshepang
Copy link
Member

I am grateful that this was removed, so 👎

@AngusP
Copy link

AngusP commented Nov 17, 2015

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;

@BurntSushi
Copy link
Member

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 👎 on this.

@AngusP
Copy link

AngusP commented Nov 17, 2015

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

@BurntSushi
Copy link
Member

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.

@arielb1
Copy link
Contributor

arielb1 commented Nov 17, 2015

@ArgusP

You certainly can have multiple expressions.

int main() {
    int x;
    return 1>0 ? ({ x=1; x; }): 2;
}

@comex
Copy link

comex commented Nov 17, 2015

(That's a GNU extension.)

@AngusP
Copy link

AngusP commented Nov 17, 2015

@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?

@sfackler
Copy link
Member

It is not particularly sustainable to support the union of all functionality that anyone has ever wanted.

@BurntSushi
Copy link
Member

I don't think any new syntax should be added under the justification of "why not."

@ticki
Copy link
Contributor

ticki commented Nov 17, 2015

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 }.

@AngusP
Copy link

AngusP commented Nov 18, 2015

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.)

@niconii
Copy link

niconii commented Nov 18, 2015

👎

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.

@pnkfelix
Copy link
Member

@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.

@AngusP
Copy link

AngusP commented Nov 18, 2015

@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.

@niconii
Copy link

niconii commented Nov 18, 2015

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.

@withoutboats
Copy link
Contributor

@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:

  • What is the benefit of this sugar to users? How much boilerplate is removed, how many characters less are typed, how does it reduce rightward drift?
  • How often would this sugar be used? Is this a common pattern?
  • How much more accessible is this sugar? Does it make code more readable, or make writing well-structured programs more at-hand?

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.

@oli-obk
Copy link
Contributor

oli-obk commented Dec 1, 2015

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.

@steveklabnik
Copy link
Member

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.

@shelby3
Copy link

shelby3 commented May 2, 2016

I made a different point about contrast given a language with block indenting instead of brace-delimited blocks.

@alilleybrinker
Copy link

How would a sans-brace syntax handle nested if-else, or multiple lines within what would have formerly been a brace-delimited block?

@shelby3
Copy link

shelby3 commented May 3, 2016

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.

@shelby3
Copy link

shelby3 commented May 4, 2016

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.

@toomaj
Copy link

toomaj commented May 29, 2016

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.

@punmechanic
Copy link

My thoughts on this are that we don't need to make Rust any more terse than it already is 😄

@raingloom
Copy link

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.

@ignitusboyone
Copy link

ignitusboyone commented Sep 1, 2016

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 }.

@nyanzebra
Copy link

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} }

@oli-obk
Copy link
Contributor

oli-obk commented Dec 6, 2016

@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

@bmFicg
Copy link

bmFicg commented May 9, 2017

5 minutes into rust first thing that i look up was Ternary operator. Im back to C. RIP.

@oli-obk
Copy link
Contributor

oli-obk commented May 9, 2017

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

@bmFicg
Copy link

bmFicg commented May 9, 2017

No interest in trolling or community . Thank you. Bye.

@punmechanic
Copy link

if you're choosing your language based on the ternary operator I think you may have somewhat larger issues :)

@le-jzr
Copy link

le-jzr commented May 9, 2017

"back to C" he said, loading his gun for another round of Russian roulette...

@BurntSushi
Copy link
Member

Really? Enough.

@rust-lang rust-lang locked and limited conversation to collaborators May 9, 2017
@Centril Centril added the T-lang Relevant to the language team, which will review and decide on the RFC. label Feb 23, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
T-lang Relevant to the language team, which will review and decide on the RFC.
Projects
None yet
Development

No branches or pull requests