Permalink
Branch: master
Find file Copy path
f1d4f6a May 3, 2018
1 contributor

Users who have contributed to this file

917 lines (727 sloc) 38.5 KB

Summary

RFC 243 left the choice of keyword for catch { .. } expressions unresolved. This RFC settles the choice of keyword. Namely, it:

  1. reserves try as a keyword in edition 2018.
  2. replaces do catch { .. } with try { .. }
  3. does not reserve catch as a keyword.

Motivation

This RFC does not motivate catch { .. } or try { .. } expressions. To read the motivation for that, please consult the original catch RFC.

For reserving a keyword

Whatever keyword is chosen, it can't be contextual.

As with catch { .. }, the syntactic form <word> { .. } where <word> is replaced with any possible keyword would conflict with a struct named <word> as seen in this perfectly legal snippet in Rust 2015, where <word> has been substituted for try:

struct try;
fn main() {
    try {
    };
}

Aside note:

The snippet above emits the following warning:

warning: type `try` should have a camel case name such as `Try`

which is also the case for catch. This warning decreases the risk that someone has defined a type named try anywhere in the ecosystem which happens to be beneficial to us.

For reserving try specifically

This is discussed in the rationale for try.

Guide-level explanation

The keyword try will be reserved. This will allow you to write expressions such as:

try {
    let x = foo?;
    let y = bar?;
    // Note: OK-wrapping is assumed here, but it is not the goal of this RFC
    // to decide either in favor or against OK-wrapping.
    x + y
}

Reference-level explanation

The word try is reserved as a keyword in the list of keywords in Rust edition 2018 and later editions.

The keyword try is used in "try expressions" of the form try { .. }.

Drawbacks

There are two main drawbacks to the try keyword.

Association with exception handling - Both a pro and con

I think that there is a belief – one that I have shared from time to time – that it is not helpful to use familiar keywords unless the semantics are a perfect match, the concern being that they will setup an intuition that will lead people astray. I think that is a danger, but it works both ways: those intuitions also help people to understand, particularly in the early days. So it’s a question of “how far along will you get before the differences start to matter” and “how damaging is it if you misunderstand for a while”.

[..]

Rust has a lot of concepts to learn. If we are going to succeed, it’s essential that people can learn them a bit at a time, and that we not throw everything at you at once. I think we should always be on the lookout for places where we can build on intuitions from other languages; it doesn’t have to be a 100% match to be useful.

- Niko Matsakis

For some people, the association to try { .. } catch { .. } in languages such as Java, and others in the prior-art section, is unhelpful wrt. teachability because they see the explicit, reified, and manually propagated exceptions in Rust as something very different than the much more implicit exception handling stories in Java et al.

However, we make the case that other languages which do have these explicit and reified exceptions as in Rust also use an exception vocabulary. Notably, Haskell calls the monad-transformer for adding exceptions ExceptT.

We also argue that even tho we are propagating exceptions manually, we are following tradition in that other languages have very different formulations of the exception idea.

The benefit of familiarity, even if not a perfect match, as Niko puts it, helps in learning, particularly because Rust is not a language in lack of concepts to learn.

Breakage of the try! macro

One possible result of introducing try as a keyword be that the old try! macro would break. This could potentially be avoided but with great technical challenges.

With the prospect of breaking try!, a few notes are in order:

  1. ? was stabilized in 1.13, November 2016, which is roughly 1.4 years since the date this RFC was started.
  2. try! has been "deprecated" since then since:

    The ? operator was added to replace try! and should be used instead.

  3. try!(expr) can in virtually all instances be automatically rustfixed automatically to expr?.
  4. There are very few questions on Stack Overflow that mention try!.
  5. "The Rust Programming Language", 2nd edition (book) and "Rust by Example" have both already removed all mentions of try!.

So overall I think it’s feasible to reduce the try! macro to a historical curiosity to the point it won’t be actively confusing to newbies coming to Rust.

- kornel

However,

  1. There are still plenty of materials out there which mention try!.
  2. try! is essentially the inverse of try { .. }.

Purging from the “collective memories of Rustaceans and Rust materials” is not something that easy.

- Manish Goregaokar

In the RFC author's opinion however, the sum total benefits of try { .. } seem to outweigh the drawbacks of the difficulty with purging try! from our collective memory.

Inverse semantics of ?

The ? postfix operator is sometimes referred to as the "try operator", and can be seen as having the inverse semantics as try { .. }.

To many, this is a drawback. To others, this makes the ? and try { .. } expression forms more closely related and therefore makes them more findable in relation to each other.

There is currently some ongoing debate about renaming the ? operator to something other than the "try operator". This could help in mitigating the effects of picking try as the keyword.

Rationale and alternatives

Review considerations

Among the considerations when picking a keyword are, ordered by importance:

  1. Fidelity to the construct's actual behavior.

  2. Precedent from existing languages

    1. Popularity of the languages.
    2. Fidelity to behavior in those languages.
    3. Familiarity with respect to their analogous constructs.

    See the prior art the rationale for try for more discussion on precedent.

  3. Brevity.

  1. Consistency with related standard library function conventions.

  2. Consistency with the naming of the trait used for ? (the Try trait). Since the Try trait is unstable and the naming of the ? operator in communication is still unsettled, this is not regarded as very important.

  3. Degree / Risk of breakage.

  4. Consistency with old learning material.

    1. Inversely: The extent of the old learning material

    That is, (in)consistency with ? and the try!() macro. If the first clause is called try, then try { } and try!() would have essentially inverse meanings.

Rationale for try

  1. Fidelity to the construct's actual behavior: Very high
  2. Precedent from existing languages: A lot, see prior-art
    1. Popularity of the languages: Massive accumulated dominance
    2. Fidelity to behavior in those languages: Very high
    3. Familiarity with respect to their analogous constructs: Very high
  3. Brevity / Length: 3
  4. Consistency with related libstd fn conventions: Consistent
  5. Consistency with the naming of the trait used for ?: Consistent
  6. Risk of breakage: High (if we assume try! will break, otherwise: Low)
    • Used in std: No, std::try!, but it is technically possible to not break this macro. (unstable: std::intrinsics::try so irrelevant)
    • Used as crate? Yes. No reverse dependencies. Described as: "Deprecation warning resistant try macro"
    • Usage (sourcegraph): 27 regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+try\s+=|(fn|impl|mod|struct|enum|union|trait)\s+try)\b
    
  7. Consistency with old learning material: Inconsistent (try!)

Review

This is our choice of keyword, because it:

  1. has a massive dominance in both popular and less known languages and is sufficiently semantically faithful to what try means in those languages. Thus, we can leverage people's intuitions and not spend too much of our complexity budget.
  2. is consistent with the standard library wrt. Try and try_ prefixed methods.
  3. it is brief.
  4. it has high fidelity wrt. the concepts it attempts to communicate (exception boundary for ?). This high fidelity is from the perspective of a programmers intent, i.e: "I want to try a bunch of stuff in this block".
  5. it can be further extended with catch { .. } handlers if we wish.

Alternative: reserving catch

  1. Fidelity to the construct's actual behavior: High
  2. Precedent from existing languages: Erlang and Tcl, see prior-art
  3. Brevity / Length: 6
  4. Consistency with related libstd fn conventions: Somewhat consistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Low
    • Used in std: No
    • Used as crate? No.
    • Usage (sourcegraph): 21 regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+catch\s+=|(fn|impl|mod|struct|enum|union|trait)\s+catch)\b
    
  7. Consistency with old learning material: Untaught

Review

We believe catch to be a poor choice of keyword, because it:

  1. is used in few in other languages to demarcate the body which can result in an exceptional path. Instead, it is almost exclusively used for exception handlers of the form: catch(pat) { recover_expr }.
  2. extending catch with handlers will require a different word such as handler to get catch { .. } handler(e) { .. } semantics if we want. This inversion compared to a lot of other languages will only harm teachability of the language and steal a lot of our strangeness budget.
  3. it is less brief than try.
  4. the consistency wrt. methods in the standard library is low - there's only catch_unwind, but that has to do with panics, not Try style exceptions.

However, catch has high fidelity wrt. the operational semantics of "catching" any exceptions in the try { .. } block.

Alternative: keeping do catch { .. }

  1. Fidelity to the construct's actual behavior: Middle
  2. Precedent from existing languages:
    • do: Haskell, Idris
    • catch: Erlang and Tcl, see prior-art
  3. Brevity / Length: 8
  4. Consistency with related libstd fn conventions: Tiny bit consistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Impossible (already reserved keyword)
    • Used in std: No, the form $ident $ident is not a legal identifier.
    • Used as crate? No, as above.
    • Usage (sourcegraph): 0 regex: N/A
  7. Consistency with old learning material: Untaught

An alternative would be to simply use the do catch { ... } syntax we have in the nightly compiler. However, this syntax was not in the accepted catch RFC and was only a temporarly fix around catch { .. } not working.

Alternative: do try { .. }

  1. Fidelity to the construct's actual behavior: High
  2. Precedent from existing languages:
    • do: Haskell, Idris
    • try: A lot, see prior-art
    1. Popularity of the languages: Massive accumulated dominance
    2. Fidelity to behavior in those languages: High
    3. Familiarity with respect to their analogous constructs: High
  3. Brevity / Length: 6 (including space)
  4. Consistency with related libstd fn conventions: Moderately consistent
  5. Consistency with the naming of the trait used for ?: Moderately consistent
  6. Risk of breakage: Impossible (already reserved keyword)
    • Used in std: No, the form $ident $ident is not a legal identifier.
    • Used as crate? No, as above.
    • Usage (sourcegraph): 0 regex: N/A
  7. Consistency with old learning material: Untaught

Review

We could in fact decide to keep the do-prefix but change the suffix to try. The benefit here would be two-fold:

  • No keyword try would need to be introduced as do already is a keyword. Therefore, the try! macro would not break.

  • An association with monads due to do. This can be considered a benfit since try can be seen as sugar for the family of error monads (modulo kinks wrt. imperative flow), and thus, the do prefix leads to a path of generality if more monads are introduced.

The drawbacks would be:

  • The wider association with monads can be seen as a drawback for those not familiar with monads.

  • do try { .. } over try { .. } adds a small degree of ergonomics overhead but not much (3 characters including the space). However, the frequency with which the try { .. } construct might be used can make the small overhead accumulate to a significant overhead when a large codebase is considered.

Other than this, the argument for do try over do catch boils down to an argument of try over catch.

Alternative: using do { .. }

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: Haskell, Idris
    1. Popularity of the languages: Haskell: Tiobe #42, PYPL #22
    2. Fidelity to behavior in those languages: Good
    3. Familiarity with respect to their analogous constructs: Poor
  3. Brevity / Length: 2
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Impossible (already reserved keyword)
  7. Consistency with old learning material: Untaught

Review

The keyword do was probably originally reserved for two use cases:

  1. do while { .. }

  2. Monadic do-notation a la Haskell:

    stuff = do
        x <- actionX
        y <- actionY x
        z <- actionZ
        sideEffect
        finalAction x y z

    The which would be translated into the following pseudo-Rust:

    let stuff = do {
        x <- actionX;
        y <- actionY(x);
        z <- actionZ;
        sideEffect;
        finalAction(x, y, z);
    };

    Or particularly for the try { .. } case:

    let stuff = try {
        let x = actionX?;
        let y = actionY(x)?;
        let z = actionZ?;
        sideEffect?;
        finalAction(x, y, z)
    };

    The Haskell version is syntactic sugar for:

    stuff =  actionX   >>=
       \x -> actionY x >>=
       \y -> actionZ   >>=
       \z -> sideEffect >>
       finalAction x y z

    or in Rust:

    let stuff =
        actionX.flat_map(|x| // or .and_then(..)
            actionY(x).flat_map(|y|
                actionZ.flat_map(|z|
                    sideEffect.flat_map(|_|
                        finalAction(x, y, z)
                    )
                )
            )
        );

    In the Haskell version, >>= is defined in the Monad typeclass (trait):

    {-# LANGUAGE KindSignatures #-}
    
    class Applicative m => Monad (m :: * -> *) where
        return :: a -> m a
        (>>=)  :: m a -> (a -> m b) -> m b
    
        (>>)   :: m a -> m b -> m b
        (>>) = \ma mb -> ma >>= \_ -> mb

    And some instances (impls) of Monad are:

    -- | Same as Option<T>
    data Maybe a = Nothing | Just a
    
    instance Monad Maybe where
        return = Just
        (Just a) >>= f = f a
        _        >>= _ = Nothing
    
    -- | `struct Norm<T> { value: T, normalized: bool }`
    data Norm a = Norm a Bool
    
    instance Monad Norm where
        return a = Norm a False
        (Norm a u) >>= f = let Norm b w = f a in  Norm b (u || w)

Considering the latter case of do-notation, we saw how try { .. } and do { .. } relate. In fact, try { .. } is special to the Try (MonadError) monads. There are also more forms of monads which you might want to use do { .. } for. Among these are: Futures, Iterators Due to having more monads than Try-based ones, using the do { .. } syntax directly as a replacement for try { .. } becomes problematic as it:

  1. confuses everyone familiar with do-notation and monads.
  2. is in the way of use for monads in general.
  3. do is generic and unclear wrt. semantics.

Alternative: reserving trap

  1. Fidelity to the construct's actual behavior: Good
  2. Precedent from existing languages: None
  3. Brevity / Length: 4
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No
    • Used as crate? No.
    • Usage (sourcegraph): 4 regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+trap\s+=|(fn|impl|mod|struct|enum|union|trait)\s+trap)\b
    
  7. Consistency with old learning material: Untaught

Review

Arguably, this candidate keyword is a somewhat a good choice.

To trap an error is sufficently clear on the "exception boundary" semantics we wish to communicate.

However, trap is used as an error handler in at least one langauge.

It also does not have the familiarity that try does have and is entirely inconsistent wrt. naming in the standard library.

Alternative: reserving wrap

  1. Fidelity to the construct's actual behavior: Somewhat good
  2. Precedent from existing languages: None
  3. Brevity / Length: 4
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No
    • Used as crate? Yes, no reverse dependencies.
    • Usage (sourcegraph): 37+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+wrap\s+=|(fn|impl|mod|struct|enum|union|trait)\s+wrap)\b
    
  7. Consistency with old learning material: Untaught

Review

With wrap { .. } we can say that it "wraps" the result of the block as a Result / Option, etc. and it is logically related to .unwrap(), which is however a partial function, wherefore the connotation might be bad.

Also, wrap could be considered too generic as with do in that it could fit for any monad.

Alternative: reserving result

  1. Fidelity to the construct's actual behavior: Somewhat good
  2. Precedent from existing languages: None
  3. Brevity / Length: 6
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very high
    • Used in std: Yes for the {std, core}::result modules.
    • Used as crate? Yes. 6 reverse dependencies (transitive closure).
    • Usage (sourcegraph): 43+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+result\s+=|(fn|impl|mod|struct|enum|union|trait)\s+result)\b
    
  7. Consistency with old learning material: Untaught

Review

The fidelity of result is somewhat good due to the association with the Result type as well as Try being a final encoding of Result.

However, when you consider Option, the association is less direct, and thus it does not fit Option and other types well.

The breakage of the result module is however quite problematic, making this particular choice of keyword more or less a non-starter.

Alternative: a smattering of other possible keywords

There are a host of other keywords which have been suggested.

fallible

On an internals thread, fallible was suggested. However, this keyword lacks the verb-form that is the convention in Rust. Breaking with this convention should only be done if there are significant reasons to do so, which do not seem to exist in this case. It is also considerably longer than try (+5 character) which matters for constructions which are oft used.

  1. Fidelity to the construct's actual behavior: High
  2. Precedent from existing languages: None
  3. Brevity / Length: 8
  4. Consistency with related libstd fn conventions: Highly inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No
    • Used as crate? Yes, some reverse dependencies (all by the same author).
    • Usage (sourcegraph) None
  7. Consistency with old learning material: Untaught

Synonyms of catch:

Some synonyms of catch have been suggested:

accept

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 6
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Medium
    • Used in std: Yes
    • Used as crate? No.
    • Usage (sourcegraph): 79+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+accept\s+=|(fn|impl|mod|struct|enum|union|trait)\s+accept)\b
    
  7. Consistency with old learning material: Untaught

capture

  1. Fidelity to the construct's actual behavior: Good.
  2. Precedent from existing languages: None
  3. Brevity / Length: 7
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Low
    • Used in std: No
    • Used as crate? Yes, no reverse dependencies.
    • Usage (sourcegraph): 6+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+capture\s+=|(fn|impl|mod|struct|enum|union|trait)\s+capture)\b
    
  7. Consistency with old learning material: Untaught

collect

  1. Fidelity to the construct's actual behavior: Very much not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 7
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very high
    • Used in std: Yes (Iterator::collect)
    • Used as crate? Yes, no reverse dependencies.
    • Usage (sourcegraph): 35+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+collect\s+=|(fn|impl|mod|struct|enum|union|trait)\s+collect)\b
    
  7. Consistency with old learning material: Untaught

recover

  1. Fidelity to the construct's actual behavior: Good
  2. Precedent from existing languages: None
  3. Brevity / Length: 7
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No
    • Used as crate? No
    • Usage (sourcegraph): 4+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+recover\s+=|(fn|impl|mod|struct|enum|union|trait)\s+recover)\b
    
  7. Consistency with old learning material: Untaught

resolve

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 7
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Low to medium
    • Used in std: No
    • Used as crate? Yes, 3 reverse dependencies
    • Usage (sourcegraph): 50+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+resolve\s+=|(fn|impl|mod|struct|enum|union|trait)\s+resolve)\b
    
  7. Consistency with old learning material: Untaught

take

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 4
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Huge
    • Used in std: Yes, {Cell, HashSet, Read, Iterator, Option}::take.
    • Used as crate? Yes, a lot of reverse dependency (transitive closure).
    • Usage (sourcegraph): 62+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+take\s+=|(fn|impl|mod|struct|enum|union|trait)\s+take)\b
    
  7. Consistency with old learning material: Untaught

Review

Of these, only recover and capture seem reasonable semantically. But recover is even more problematic than catch because it enhances the feeling of exception-handling instead of exception-boundaries. However, capture is reasonable as a substitute for try, but it seems obscure and lacks familiarity, which is counted as a strong downside.

and some other keywords:

coalesce

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 8
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Medium (itertools)
    • Used in std: No.
    • Used as crate? Yes, one reverse dependency.
    • Usage (sourcegraph): 3+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+coalesce\s+=|(fn|impl|mod|struct|enum|union|trait)\s+coalesce)\b
    
  7. Consistency with old learning material: Untaught

fuse

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 4
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Medium (libstd)
    • Used in std: Yes, Iterator::fuse.
    • Used as crate? Yes, 8 reverse dependencies (transitive closure).
    • Usage (sourcegraph): 8+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+fuse\s+=|(fn|impl|mod|struct|enum|union|trait)\s+fuse)\b
    
  7. Consistency with old learning material: Untaught

unite

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 5
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No.
    • Used as crate? No.
    • Usage (sourcegraph): 0+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+unite\s+=|(fn|impl|mod|struct|enum|union|trait)\s+unite)\b
    
  7. Consistency with old learning material: Untaught

cohere

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 6
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No.
    • Used as crate? No.
    • Usage (sourcegraph): 0+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+cohere\s+=|(fn|impl|mod|struct|enum|union|trait)\s+cohere)\b
    
  7. Consistency with old learning material: Untaught

consolidate

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 11
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No.
    • Used as crate? No.
    • Usage (sourcegraph): 0+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+consolidate\s+=|(fn|impl|mod|struct|enum|union|trait)\s+consolidate)\b
    
  7. Consistency with old learning material: Untaught

unify

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 5
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No.
    • Used as crate? Yes, no dependencies
    • Usage (sourcegraph): 1 regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+take\s+=|(fn|impl|mod|struct|enum|union|trait)\s+take)\b
    
  7. Consistency with old learning material: Untaught

combine

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 7
  4. Consistency with related libstd fn conventions: Inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Medium
    • Used in std: No.
    • Used as crate? Yes, 17 (direct dependencies)
    • Usage (sourcegraph): 6+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+combine\s+=|(fn|impl|mod|struct|enum|union|trait)\s+combine)\b
    
  7. Consistency with old learning material: Untaught

resultof

  1. Fidelity to the construct's actual behavior: Somewhat
  2. Precedent from existing languages: None
  3. Brevity / Length: 8
  4. Consistency with related libstd fn conventions: Very inconsistent (not verb)
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage: Very low
    • Used in std: No.
    • Used as crate? No.
    • Usage (sourcegraph): 0+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+resultof\s+=|(fn|impl|mod|struct|enum|union|trait)\s+resultof)\b
    
  7. Consistency with old learning material: Untaught

returned

  1. Fidelity to the construct's actual behavior: Not at all.
  2. Precedent from existing languages: None
  3. Brevity / Length: 8
  4. Consistency with related libstd fn conventions: Very inconsistent
  5. Consistency with the naming of the trait used for ?: Inconsistent
  6. Risk of breakage:
    • Used in std: No.
    • Used as crate? No.
    • Usage (sourcegraph): 0+ regex:
    repogroup:crates case:yes max:400
    \b((let|const|type|)\s+returned\s+=|(fn|impl|mod|struct|enum|union|trait)\s+returned)\b
    
  7. Consistency with old learning material: Untaught

Review

Of these, only resultof seems to be semantically descriptive and has some support. However, it has three major drawbacks:

  • Length: Compared to try, it is 5 characters longer (see reasoning for fallible).

  • Not a word: resultof is in fact a concatenation of result and of. This does not feel like a natural fit for Rust, as we tend to use a _ separator. Furthermore, there are no current keywords in use that are concatenations of two word.

  • Result<T, E> oriented: resultof is too tied to Result<T, E> and fits poorly with Option<T> or other types that implement Try.

Prior art

All of the languages listed below have a try { .. } <handler_kw> { .. } concept (modulo layout syntax / braces) where <handler_kw> is one of: catch, with, except, trap, rescue.

In total, these are 29 languages and they have massive ~80% dominance according to the TIOBE index and roughly the same with the PYPL index.

The syntactic form catch { .. } seems quite rare and is, together with trap, rescue, except, only used for handlers. However, the <kw> { .. } expression we want to introduce is not a handler, but rather the body of expression we wish to try.

There are however a few languages where catch { .. } is used for the fallible part and not for the handler, these languages are:

However, the combined popularity of these langauges are not significant as compared to that for try { .. }.

Unresolved questions

None as of yet.