NHacker Next
login
▲Weird Expressions in Rustwakunguma.com
100 points by lukastyrychtr 5 hours ago | 72 comments
Loading comments...
steveklabnik 4 hours ago [-]
This post is missing my favorite one!

    fn evil_lincoln() { let _evil = println!("lincoln"); }
What's weird about this?

To understand what evil_lincoln is doing, you have to understand very old Rust. Here's the commit that introduced it: https://github.com/rust-lang/rust/commit/664b0ad3fcead4fe4d2...

    fn evil_lincoln() {
        let evil <- log "lincoln";
    }
log was a keyword to print stuff to the screen. Hence the joke, https://en.wikipedia.org/wiki/Lincoln_Logs Now that log is the println! macro, the joke is lost.

It doesn't say explicitly why this is "weird", but given some other comments in the file,

    // FIXME: Doesn't compile
    //let _x = log true == (ret 0);
I am assuming that using the return value of log was buggy, and so this tested that you could save it in a variable. I don't remember the exact semantics of log, but if it's like println!, it returns (), which is useless, so binding it to a variable is something you'd never write in real code, so it's "weird" in that sense.
ibotty 3 hours ago [-]
What's the joke exactly? English is not my native language.
jerf 3 hours ago [-]
https://www.basicfun.com/lincoln-logs/

This would be something the Boomer generation grew up with, and I think maybe the previous generation too. They're still around but they've certainly faded; they used to be Lego-level popular kids toys back then. They are named after President Lincoln, but only as a marketing tactic to use some of his reputation, there's no real connection.

I would imagine even some native English speakers are learning something with this post. I haven't seen them in a while.

drfuchs 2 hours ago [-]
There’s a strong connection between President Lincoln and log cabins. He grew up in a series of log cabins, and this fact was widely known during his campaign.
saghm 46 minutes ago [-]
I'm a late millennial, and I'd sometimes see them as a kid too. I'm not sure about more recent generations, but I think that they might have stuck around longer than you might think.
ranguna 3 hours ago [-]
Yes, but why is it evil?
Analemma_ 2 hours ago [-]
I think that part is a reference to a Futurama episode where a holodeck malfunction materialized several villains, including "Evil Lincoln".
bennettnate5 1 hours ago [-]
> They were named after President Lincoln, but only as a marketing tactic

> there's no real connection

Funny--I always thought it was meant to be a pun on linkin', as in you're linkin' the logs together because they have those slots that fit precisely together on the ends.

saghm 44 minutes ago [-]
I think it's both that and the popular tale of Lincoln having been born in a log cabin (which for some reason I thought I had heard wasn't actually true, but from looking into it now, it seems like a lot of sources say it is, so maybe I heard wrong?)
rendaw 3 hours ago [-]
What's the joke exactly? English is my native language.
steveklabnik 3 hours ago [-]
log "lincoln" is a reference to the toy "lincoln logs"
b0a04gl 4 hours ago [-]
they exist because whole language built to treat expressions as firstclass citizens : blocks, ifs, matches, even macros as expressions that return values. so once you internalize that, all these weirdo one liners are artifacts. just artifact of a system where expressions compose infinitely. the syntax tree runs deeper than most people's habbits allow. you hit that depth and brain says this is wrong but compiler's allowing.
AIPedant 1 hours ago [-]
Hmm my read is this is a slight overstatement - Rust was always built with the idea of expressions as first class citizens, but practicality and performance requires expression-breaking keywords like “return” which don’t fit neatly in an ML-ish language and have a few plain old hacks associated with implementing them (not “hack” as in lacking robustness; I mean theoretically/formally inelegant). Likewise there’s some stuff (u8) which is a simple syntax quirk. But a lot of these return/etc oddities are because Rust is ultimately an imperative language with strong influence from functional programming.
steveklabnik 50 minutes ago [-]
return is an expression in Rust, and it fits in well.

There are very few statements: https://doc.rust-lang.org/stable/reference/statements.html

and a lot of expressions: https://doc.rust-lang.org/stable/reference/expressions.html

efnx 21 minutes ago [-]
I’ve found rust to be an ML in C’s clothing.

The main difference from other MLs is the lack of higher kinded types, so it’s difficult to express things like Functor, Monad, Arrow, etc

derriz 2 hours ago [-]
That sounds superficially reasonable to me and I'm all for regularity in programming language semantics but on thinking about it further, I actually think it's a design flaw.

It makes no more sense to me for "return <expr>" to have a type than it does to make "if <expr>" or "break" or "{" or any other keyword to have a type. These are syntactic elements.

Rust's type system is clearly inspired by Hindley-Milner and most languages using such a type system either don't even have a return keyword.

Even if you disagree with this argument, this design decision has resulted in all these weird/confusing but absolutely useless code examples and there is no upside that I can see to this decision in terms of language ergonomics. What practical value is it to users to allow "return <expr>" to itself be an expression? That you can use such an "expression" as arguments to function calls with hilarious wtf consequences? It's a piece of syntactic sugar.

steveklabnik 2 hours ago [-]
Respectfully, "it makes no sense to me" isn't an argument. if and break both have types in Rust as well.

> don't even have a return keyword.

This is because they are not procedural languages, it has nothing to do with the type system.

> there is no upside that I can see to this decision in terms of language ergonomics.

There's tremendous upside! That's why lots of languages choose this. For example, there is no need for the ternary in Rust: if can just do that.

> What practical value is it to users to allow "return <expr>" to itself be an expression?

Code like this just works:

        let guess: u32 = match guess.trim().parse() {
            Ok(num) => num,
            Err(_) => return,
        };
That is, if return wasn't an expression, we'd have a type error: the two arms would have incompatible types.
derriz 20 minutes ago [-]
See my comment above, your example only "just works" if the enclosing function has the appropriate return type (in this case none).

So the syntactic element "return" is not just an expression - unlike other sub-expressions, it involves action at a distance - i.e. it must not just agree with it's context as part of an expression but it must agree with the enclosing fn signature.

steveklabnik 17 minutes ago [-]
I replied over there, let's keep it to that sub-tree so we both don't have to duplicate comments :)
bobbylarrybobby 2 hours ago [-]
The issue with `return expr` not having a type is that you lose the ability to write something like

let y = match option { Some(x) => x, None => return Err("whoops!"), };

Without a type, the None branch loses the ability to unify with the Some branch. Now you could say that Rust should just only require branches’ types to unify when all of them have a type, but the ! never type accomplishes that goal just fine.

derriz 27 minutes ago [-]
I'm responding here because so many replies are making the same point.

In your particular example, let's put your example into a context. Is

  fn foo(option: Option<i32>) -> i32 {
     let y = match option { Some(x) => x, None => return Err("whoops!"), };
     return 1;
  }
well typed? It should be if we are to believe that "return <expr>" is an expression of type () - but, naturally, it causes a compilation error because the compiler specifically treats "return <expr>" unlike other expressions. So there is no improvement in regularity, while it admits all sorts of incomprehensible "puzzlers".

I don't see why you'd lose this ability if you removed the claim that "return <expr>" is itself an expression. Most/many languages have mechanisms to allow expressions to affect flow control - e.g. with exceptions, yield, etc. - which do not these constructs (for example "throw x") to have a type.

Rust could just as easily supported the syntax you use above without making "return <expr>" a tapeable expression.

steveklabnik 21 minutes ago [-]
> Is ... well typed?

It's not, but not due to the return, it's because you're trying to return a Result from a function that returns an i32. This works:

  fn foo(option: Option<i32>) -> Result<i32, &'static str> {
     let y = match option { Some(x) => x, None => return Err("whoops!"), };
     return Ok(1);
  }
> It should be if we are to believe that "return <expr>" is an expression of type ()

It is not, it is an expression of type !. This type unifies with every other type, so the overall type of y is i32. return is not treated in a special way.

> if you removed the claim that "return <expr>" is itself an expression

This code would no longer work, because blocks that end in an expression evaluate to (), and so you would get the divergent, not well typed error, because one arm is i32 and the other is ().

derriz 10 minutes ago [-]
Sorry for the confusion - I meant to use ! and not ().

"It's not, but not due to the return, it's because you're trying to return a Result from a function that returns an i32."

That's exactly my point. "return <expr>" is not just an expression which can be typed. If you tell me the types of all the identifiers used, I can look at any expression in Rust which does not include a return, and tell you if it's well typed or not. If the expression includes a return, then I cannot tell you whether the expression is well-formed.

steveklabnik 6 minutes ago [-]
> "return <expr>" is not just an expression which can be typed.

Yes, it is, and it can. It has the type !, no matter the type of <expr>.

bobbylarrybobby 17 minutes ago [-]
The type of return is !, not (). Meaning there are zero instances of this type (whereas there is one instance of ()). ! can coerce to any type.

Also, the type of return is a separate matter from the type of the thing being returned. You obviously can't return Result from a function returning i32. The point of type coercion is that you can yield `return Err(...)` in one branch of a match and have it type check with the other branch(es).

efnx 18 minutes ago [-]
Well now we’re just talking about personal preferences, then.
NobodyNada 2 hours ago [-]
In practice, it's quite a useful feature, because you can write things like this:

    let day_number = match name {
        "Sunday" => 0,
        "Monday" => 1,
        "Tuesday" => 2,
        "Wednesday" => 3,
        "Thursday" => 4,
        "Friday" => 5,
        "Saturday" => 6,
        _ => return Err("invalid day")
    };
pornel 1 hours ago [-]
This makes the language more uniform. Instead of having a special ternary ?: operator for if-else in expression position, you have one syntax everywhere.

It makes generic code work without need to add exceptions for "syntactic elements". You can have methods like `map(callback)` that take a generic `fn() -> T` and pass through `T`. This can work uniformly for functions that do return values as well as for functions that just have `return;`. Having nothingness as a real type makes it just work using one set of rules for types, rather than having rules for real types plus exceptions for "syntactic elements".

deathanatos 2 hours ago [-]
We can use match to do pattern matching:

  let name = match color_code {
    0 => "red",
    1 => "blue",
    2 => "green",
    _ => "unknown",
  };
The RHS of the `=>` has to be an expression, since we're assigning it to a variable. Here, you should already see one "useful" side-effect of what you're calling "syntactic elements" (I'd perhaps call them "block statements", which I think is closer to the spirit of what you're saying.) The whole `match … {}` in the example above here is an expression (we assign the evaluation of it to a variable).

> What practical value is it to users to allow "return <expr>" to itself be an expression?

Now, what if I need to return an error?

  let name = match color_code {
    0 => "red",
    1 => "blue",
    2 => "green",
    _ => return Err("unknown color"),
  };
The expression arms need to be the same type (or what is the type of `name`?). So now the type of the last branch is !. (Which as you hopefully learned from TFA, coerces to any type, here, to &str.)

There's more ways this "block statements are actually expressions" is useful. The need not be a ternary operator / keyword (like C, C++, Python, JS, etc.):

  let x = if cond { a } else { b };
In fact, if you're familiar with JavaScript, there I want this pattern, but it is not to be had:

  const x;  // but x's value will depend on a computation:
  // This is illegal.
  if(foo) {
    x = 3;
  } else {
    x = 4;
  }
  // It's doable, but ugly:
  const x = (function() { if(foo) { return 3; } else { return 4; }})();
  // (Yes, you can do this example with a ternary.
  // Imagine the if branches are a bit more complicated than a ternary,
  // e.g., like 2 statements.)
  
Similarly, loops can return a value, and that's a useful pattern sometimes:

  let x = loop {
    // e.g., find a value in a datastructure. Compute something. Etc.
    if all_done {
      break result;
    }
  };
And blocks:

  let x = {
    // compute x; intermediate variables are properly scoped
    // & cleaned up at block close.
    //
    // There's also a slight visual benefit of "here we compute x" is
    // pretty clearly denoted.
  };
> Even if you disagree with this argument, this design decision has resulted in all these weird/confusing but absolutely useless code examples

I think one can cook up weird code examples in any language.

dathinab 1 hours ago [-]
it doesn't need to make sense on a higher abstraction level of logic/semantics

I mean you don't see any of the nonsense in the blog post in any realistic PR (so they don't matter),

but you would run into subtle edge case issues if some expressions where more special then other expressions (so that does matter),

especially in context of macros/proc macros or partial "in-progress" code changes (which is also why `use` allows some "strange" {-brace usage or why a lot of things allow optional trailing `,` all of that makes auto code gen simpler).

throwawaymaths 2 hours ago [-]
its not just that some things you would usually think are control flow are expressions, its also that there are unusual rules around coercing the `noreturn` type.
gmueckl 2 hours ago [-]
If a language that claims to be security focused is easily able to express constructs that human minds find barely comprehensible, or worse, then this is itself arguably a security issue: it's impossible to check the correctness of logic that is incomprehensible.
pornel 1 hours ago [-]
What's the threat model? If you're reviewing untrusted or security-critical code and it's incomprehensible, for any reason, then it's a reject.

Syntax alone can't stop sufficiently determined fools. Lisp has famously simple syntax, but can easily be written in an incomprehensible way. Assembly languages have very restrictive syntax, but that doesn't make them easy to comprehend.

Rust already has a pretty strong type system and tons of lints that stop more bad programs than many other languages.

gmueckl 47 minutes ago [-]
Many modern language designers focus on shaping expressibility rather than providing the maximum possible flexibility because their designers learned from C, Lisp and other languages that made mistakes. Examples lamguages are Java, C#, D, Go... some arguably with more success than others. But language design that gave ultimate expressive power to the the programmer is a relic of the past.
steveklabnik 2 hours ago [-]
> If a language that claims to be security focused

Rust does not claim to be particularly security-focused, only memory safe.

Also, this means that you'd consider any expression-based language to be inherently a security problem.

gmueckl 2 hours ago [-]
"Memory safety" is an aspect of computer security. And security is the first listed value in rust's mission statement.

Rust is not written as a pure expression based language. And as we all know very well from the experience with C and JS, any unexpected and weird looking code has the potential to hide great harm. Allowing programmers to stray too much from expected idioms is dangerous.

steveklabnik 2 hours ago [-]
It is an aspect, but it Rust does not promise your core is secure.

It’s not purely expression based but it is very close to it, there’s only a few kinds of statements, the vast majority of things are expressions.

keybored 20 minutes ago [-]
We would need an example of puzzling code that can easily hide (security) bugs.

The submission shows weird program snippets. I don’t think it shows weird snippets that can also easily hide bugs?

PaulHoule 2 hours ago [-]
It is a critique of macros. 500 lines of Common Lisp replaces 50,000 lines of C++ but those 500 lines make no sense at all the first time you see them.
munificent 20 minutes ago [-]
Does anyone know why `union` isn't a reserved word in Rust?

Most contextual keywords in other languages come from either:

1. Features that were added after the language was in wide use and can't add keywords without breaking existing code.

2. Features where the word is particularly useful elsewhere, so would be painful to reserve (like `get` and `set` in Dart).

But neither of those seem to apply to Rust. As far as I know, it's always had ML-style unions, and "union" doesn't seem to be a particularly useful identifier otherwise.

Why isn't `union` fully reserved?

pitaj 4 minutes ago [-]
It's a common operation on sets, so would make `HashSet::union` [1] and friends less obvious, for no real benefit.

[1] https://doc.rust-lang.org/stable/std/collections/struct.Hash...

steveklabnik 17 minutes ago [-]
It's simply that Rust has higher standards for breaking changes than "probably not in wide use." In other words, that someone could have had `let union =`... somewhere was a reason to make it contextual.

https://rust-lang.github.io/rfcs/1444-union.html#contextual-...

pluto_modadic 24 minutes ago [-]
I've absolutely seen some /cursed/ rust one liners.

if you extend it to the most cursed ~6 lines of code, you really can obfuscate what you're doing in a way that's fiendishly hard to debug.

ramon156 4 hours ago [-]
Note that for Rust devs these are also weird syntaxes. I feel like some people assume that an experienced dev can read these, but it takes a while to get what's going on.
ChuckMcM 3 hours ago [-]
Kind of like obfuscated C code I suspect.
dathinab 1 hours ago [-]
yes, but less risky (and less power full) because you often very fast can conclude that "whatever it does it's safe, sound and doesn't affect unrelated code"
arjvik 1 hours ago [-]
I figured out how return-as-a-value made sense only upon realizing that in the following code,

    fn funny(){
        fn f(_x: ()){}
        f(return);
    }
f() is never called because funny() returns before it gets called.

The reason you want return to be coercible to any type is so that you can write something like

    let x: i32 = if y {
        4
    } else {
        return; // type ! coerced into i32
    }
And you pick the return value of ! because return never actually produces a value that is propagated on at runtime, it immediately exits the function.

(Note this all works even with returning a value)

JoeOfTexas 9 minutes ago [-]
Bruh, I started learning Rust yesterday. Why do you do this to me. Now I don't know anything I studied.
steveklabnik 5 minutes ago [-]
You don't need to know any of this. It's just a parsing stress test, with meaningless programs. It's fun trivia.
kzrdude 54 minutes ago [-]
Many of them are on the same theme - the theme is `return -> !`.

Here's my favourite on that theme, which I was missing from the list:

    return return return return return return return return return 1
Sniffnoy 2 hours ago [-]
I don't understand the one with dont(). Why does i.get() end up false at the end? Shouldn't it be true after having been set by dont()?
LegionMammal978 54 minutes ago [-]
The final line "assert!(i.get());" asserts that i.get() is true in the end. The ! character here belongs to the assert! macro, not a boolean negation. (This unfortunately gets a bit weird-looking when you want to write sensible stuff like "if !matches!(x, Some(5..=10)) { ... }".)
armchairhacker 2 hours ago [-]
Related: https://dtolnay.github.io/rust-quiz

Rust programs that give unintuitive outputs or compile errors.

behnamoh 4 hours ago [-]
Great, now this stuff gets fed into LLM trainings and we'll see them in the next-gen model outputs.

Seriously though, I love "abusing" programming languages in unexpected ways. My favorite so far is: https://evuez.net/posts/cursed-elixir.html. Reading this made me realize Elixir is literally macros all the way down, and it's a Lisp!

IshKebab 3 hours ago [-]
Honestly I'm surprised how not weird these are. Way less WTFy than Javascript, PHP, C or C++.
dathinab 52 minutes ago [-]
yes but to be fair the blog is focused on unusual aspects of everything being an expression

you still can create some more confusing things by idk. overloading some operators (but luckily not `=` and similar crazy C++ things) adding recursive macros and maybe combining it with lifetime variance and coercion edge cases, maybe sprinkle in some arcane `#[]` annotations and people with be very confused, more so then in the article

IshKebab 19 minutes ago [-]
Yeah... I'm just saying I haven't seen anything close to these things:

https://github.com/denysdovhan/wtfjs

https://github.com/satwikkansal/wtfpython

npalli 3 hours ago [-]
yeah this is good, but now add lifetime annotations to have fun.
steveklabnik 3 hours ago [-]
Lifetimes are not expressions and therefore can't really be put into odd places.
Thaxll 3 hours ago [-]
Why assigning a variable to a function that returns nothing is not a compilation error?
tialaramex 1 hours ago [-]
Do you mean a function which returns "nothing" in the sense that it does return but it has no particular value to return, like Vec::clear which gets rid of the values but preserves the capacity of the container ?

In Rust the return type of this function is the unit type, the empty tuple (). So, the variable has this type, there's no problem with this in Rust, even though some lesser languages can't handle the idea of a type this small.

Or did you mean a function which never returns, like std::process::exit ? In Rust this function's return type is ! the Never type, an empty type that you ordinarily can't name in stable Rust.

Because this type is empty, a variable of this type will evaporate, the compiler knows that we can't bring values into existence if there are no values of that type, the code paths in which this variable exists will never be executed, so no need to emit machine code.

In a language with generic programming like Rust this isn't an error, it's actually a convenience. We can write generic error handling code, and then for cases where there will never be an error our error handling code doesn't even compile, it evaporates entirely, yet for cases which can have actual errors, the error handling code is emitted.

dathinab 55 minutes ago [-]
assuming you mean returning () (the empty tuple/void type)

because it doesn't compose well with generics, macros, proc-macros etc.

e.g. if you have this dump way to wrap clone: `fn foo<T: Clone>(v: T) -> (T, T) { let new = v.clone(); (v, new) }` it would implicitly not work with T = (), because then `v.clone()` would be a "function which returns nothing".

In isolation this might seem fine but if you compose abstractions you sooner or later run into an edge case where it isn't fine.

And this becomes even more a problem when macros/proc-macros are involved.

It also makes changing code easier, lets say you have something like `let x = foo(); dbg!(x);` and you change the return type it will keep compiling as long as the type implements `Debug`, even if you change the type to `()` (i.e. return nothing). And again for normal code that is a minor nit pick, but for macros, proc macros, generic code of sufficient complexity sooner or later you run into edge cases where it really matters that it's allowed. Not often but often enough.

Lastly and most importantly assigning `()` to a variable hurts no one, you won't see any such code in normal PRs.

So it it doesn't hurt anyone but can be quite use full in edge cases.

Lastly linters (mainly clippy) do warn or error for some of this nonsensical things, depending on the lint-set you enabled.

steveklabnik 3 hours ago [-]
Which example are you referencing?
assbuttbuttass 2 hours ago [-]
There's no such thing as a "function that returns nothing" in Rust. Unit, written as (), is a first class value and can be assigned to a variable, although there's not much point
xyst 4 hours ago [-]
These “weird expressions” probably get used code golf.
steveklabnik 4 hours ago [-]
Basically none of them are actually useful, or even do anything, it's mostly a parser stress test.
nikolayasdf123 4 hours ago [-]
this is why I like Go
nemo1618 3 hours ago [-]
I wonder, what's the "weirdest" expression in Go? Here's one:

   type Foo struct{}
   func (Foo) Bar() { println("weird...") }
   func main() {
    ([...]func(){^^len(`
   
   
   `): (&Foo{}).Bar})[cap(append([]any(nil),1,2,3))]()
   }
assbuttbuttass 2 hours ago [-]
Makes sense to me, [...]func() is an array of functions, and [...]T{index: value} is uncommon but still perfectly comprehensible
nemo1618 2 hours ago [-]
Many people aren't aware that you can use key: val declarations in arrays
jenadine 2 hours ago [-]
That's why I like Rust /s
timeon 3 hours ago [-]
It does not have stress tests for parser?