9 ms·
Rust for JavaScript Developers – Functions and Control Flow
- hardwaregeek 6y agoIt'd be nice to mention that Rust has if expressions, not if statements. Therefore the example could be simplified to: fn calculate_tax(income: i32) -> i32 { if income < 10 { 0 } else if income >= 10 && income < 50 { 20 } else { 50 } }
- yen223 6y agoAlso worth pointing out that if expressions is the Rust alternative to Javascript's ternary operator
- rkwz 6y agoThanks for reading! Will mention this in future chapters :)
- 65536 6y agoSpeaking of which, coming from Rust (well, when I say it like that it sounds like Rust is the only language I ever used which is of course not true. I think Rust as a first language would be highly unusual still), I enjoyed being able to say Rust code: let foo = if x <= 0 { 0 } else if something_else > 50 { 9000 } else { 42 }; And I miss this in Swift. The ternary operator is fine on its own but I’m not a huge fan of nested ternary operators. With a bit of whitespace it's somewhat readable still but still not as comfortable and easy to read correctly. Swift code: let foo = x <= 0 ? 0 : (somethingElse > 50 ? 9000 : 42 ) One way of writing something that is more similar to the way I'd write it in Rust would be to make a closure and then run it. Swift code: let foo = { () -> Int in if x <= 0 { return 0 } else if somethingElse > 50 { return 9000 } else { return 42 } }() But it's a bit annoying still, both to read and to write. Also, even in the current version of Swift (Swift 5), the compiler cannot infer the return type of the closure on its own even though all of the branches return an Int, so I'd have to explicitly annotate that as I have done in the code above. I guess for a lot of people they would just make foo mutable and write the code as Swift code: var foo = 42 if x <= 0 { foo = 0 } else if somethingElse > 50 { foo = 9000 } I concede that this is probably the most readable out of all of the three Swift code samples in my comment. But the point is that I didn't want foo to be mutable. I wanted to assign a value to it based on some simple logic and have it be immutable.
- w-ll 6y agoI'd guess i fall into the mutable camp, while sometimes doing a closure. Nested ternary if statements scare me from my PHP days, and noticed inlined closures can at times be harder to grok at a glance.
- bobbylarrybobby 6y agoI find ternary operators perfectly readable if you split the parts across lines — they map exactly to the conditions and statements in an if-else group. Cond1 // if this ? Result1 // return this : cond2 // else if this ? Result2 // return this : cond3 // else if this ? Result3 // return this : ResultElse // else return this
- pests 6y agoWhich is not true in PHP up until the most recent version (iirc) due to it being left associative instead of right associative you are assuming.
- amenod 6y agoTrue, or even: Cond1 // if this ? Result1 // return this : cond2 // else if this ? Result2 // return this : cond3 // else if this ? Result3 // return this : ResultElse // else return this
- giantDinosaur 6y agoGoodness me that has potential to become something truly hideous.
- bryanrasmussen 6y agoI don't know Rust - but is it possible to write it like this? fn calculate_tax(income: i32) -> i32 { if income < 10 { 0 } if income < 50 { 20 } 50 } on edit: tried to fix formatting but didn't work.
- vpzom 6y agoNo, if expressions can basically only be used with else, otherwise you have a type mismatch
- conradludgate 6y agoIt wouldn't work like that but if you made them else if and else branches respectively then yes it would. Each if/if else/else block as a whole is a single statement. But two if statements and a literal is three individual statements
- TheDong 6y agoIf you wanted to list out cases without having to use `if`, `else if`, you'd idiomatically use match: fn calculate_tax(income: i32) -> i32 { match income { 0..10 => 0, 10..50 => 20, _ => 50, } } See this playground for a couple other alternatives using match too https://play.rust-lang.org/?version=nightly&mode=debug&edition=2018&gist=1e7b3da5d8821ee99ee6d7f6936b0e21 https://play.rust-lang.org/?version=nightly&mode=debug&editi... As others explained, you do need a default case (either an 'else' in your if statement, or an exhaustive match), otherwise how would the expression typecheck? For a concrete example, if you use an 'if' only, you can have the following: let x = if income < 10 { 0 }; At that point, there's no way to statically know x's type. If income is >= 10, x is what? In order to convince the compiler x has a statically knowable type, the "if" expression need an else statement, or should be converted into a match that the compiler can prove is exhaustive.
- bad-apple 6y agoNitpick: I'm pretty sure that "11..50" should be a "10..50".
- eximius 6y agoRust can also match on ranges which is perhaps the most idiomatic form for this.
- delta1 6y agofn calculate_tax(income: i32) -> i32 { match income { 0..=9 => 0, 10..=49 => 20, _ => 50, } } https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=64cd9c40d4783dc95dbdb38ae2824c55 https://play.rust-lang.org/?version=stable&mode=debug&editio...
- nyanpasu64 6y agoThis behaves differently for negative incomes, which are not prohibited by the i32 argument passed in (u32 cannot take on negative values).
- jFriedensreich 6y agoCould someone explain why there is no dereference in let double = |n: &i32| -> i32 { n * 2 };
- rkwz 6y agoThis SO answer explains the reasoning - https://stackoverflow.com/questions/54100030/why-do-i-need-to-dereference-a-variable-when-comparing-it-but-not-when-doing-ari https://stackoverflow.com/questions/54100030/why-do-i-need-t... :) Edit: On hindsight, I should've picked a simpler example that doesn't deal with references.
- MuffinFlavored 6y agoI just converted a relatively simple node.js program to Rust (because the native libusb bindings for node.js crash immediately on Windows 7 64-bit) Rust made me worry about a lot more than I wanted to. Threads, locking, mutability. What was 80 lines of: 1. open USB device, get handle 2. start a WebSocket server, accept clients 3. proxy incoming USB device traffic to all WebSocket clients 4. proxy incoming WebSocket traffic to USB device in node.js turned into battles with dyn FnMut traits to pass "callback/event" handlers around, spawning threads to not block on libusb reading / WebSocket client reading, borrowing, cloning, mutex locking, reference counting with Rust like you can in JavaScript. You can't just "move variables from one scope into callback/lambda" scope with any sort of ease in Rust.
- bluejekyll 6y agoIt sounds a bit like you tried to write Rust like you do JS. Rust is “move” by default. Passing ownership of a variable to callbacks requires making sure you have the correct types along the way, especially if you want to mutate. It can be easier in many cases to only deal with “owned” data in Rust at first. Clone a lot, and so on.
- politicdone 6y agorust is for losers
- aaomidi 6y agoCurious why didn't you use promises in the nodejs code?
- steelbrain 6y agoWhat part of their reply indicated they did not?
- aaomidi 6y agoMaybe I misread
- 29athrowaway 6y agoDoing currency manipulation using with i32 as an example for developers of a language that only supports float. What can go wrong? Not only this is a bad idea from a programming perspective, or a teaching perspective, it is also a bad idea from a taxation perspective. In terms of taxation, what you suggest would result in [at least] civil penalties. If you are going to create examples of code that manipulates currency, at least take it seriously and think about what would actually happen to people if they decided to use your code. Otherwise, use another concept to illustrate your example.
- aaomidi 6y agoYou're being harsh but yeah I kinda agree with you.
- kybernetikos 6y ago> Doing currency manipulation using with i32 as an example for developers of a language that only supports float. What can go wrong? Using i32 for currency calculations is probably a bad idea (although there are situations where it would be OK), but this has absolutely nothing to do with javascript - it's no worse in js than it is in rust, since javascript fully supports all i32 numbers. In fact, it has a number of bitwise operators that operate on i32 numbers, and a bitwise operator that even works on u32 numbers.
- elramon 6y agoNeat! Thank you so much for this series, you have made a great work explaining complex concepts in a simple way.
- oaiey 6y agoNow it is official: I like (modern) JavaScript. I am a C# guy and every time I see modern Rust, C++ or Go syntax I am really surprised by its deviation from the norm (which is interestingly no longer C but a hybrid between JS/Java/C#). I mean why different Rust syntax for closures and functions. || vs (). On the other hand, the linage is not Java/JS/C# to Rust but C/C++ to Rust. Considering that, it is an improvement.
- chrismorgan 6y agoRust has a philosophy of easy machine and human parsing. ECMAScript-style `(…) => …` requires that you get to the => before you know whether the (…) is a parenthesised expression or function arguments. With `|…| …`, you know you’re dealing with a closure immediately because there’s no unary pipe operator.
- entha_saava 6y agowhat about `fn(args) => exp`? Can be simplified as `fn arg => exp(arg)` for simple ones..
- chrismorgan 6y agoThat works for JavaScript, because functions are just functions. But in Rust, closures are different from functions, though they all implement the Fn, FnMut or FnOnce traits. Functions don’t close over any state (so you can cast them to a 'static function pointer), while closures can. Using the fn keyword for closures would thus muddy the conceptual waters—though you could argue that the names of the Fn* traits has already done that—and potentially hinder future language design. I feel a stronger argument is that it’s also markedly longer and more visually noisy; `x.map(|x| …)` is seven characters shorter than `x.map(fn x => …)` and eight than `x.map(fn(x) => …)`, and to some extent words are more distracting than symbols. Pipes isn’t perfect, but given the consistent philosophies of the Rust language, I am not aware of any better option. But it’s definitely a fairly subjective matter.
- esperent 6y ago
- dgb23 6y ago> Arrow functions are a popular feature in modern JavaScript - they allow us to write functional code in a concise way. > Rust has something similar and they are called “Closures”. The name might be a bit confusing (...) This is in turn confusing to me! I can't imagine a world where you know JS and especially "functional" JS, but haven't heard of the term "closure". "Arrow functions" are just sugar. They don't have any special meaning. All functions in JS, regardless of whether they are declared, assigned, returned etc., are closures and capture their environment. Similarly a normally declared function in Rust are first class values as well and for example can be passed into higher order functions. Giving the benefit of the doubt, I assume that the author knows this and just wanted to introduce the term in a beginner friendly manner. But I think it would be less confusing to just use the term and link to an official documentation: Rust: https://doc.rust-lang.org/book/ch13-01-closures.html https://doc.rust-lang.org/book/ch13-01-closures.html JS: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Closures https://developer.mozilla.org/en-US/docs/Web/JavaScript/Clos...
- esperent 6y ago> "Arrow functions" are just sugar This statement is incorrect. There are important differences between arrow functions and regular function. > An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
- dgb23 6y agoI include this in sugar as in: You can refactor an arrow function into a specific regular function but you usually end up with something more verbose. Or in other words you can express all things "arrow" before it existed. Which is exactly what we did before they were adopted. And we still in fact do, just automated by transpilers like Babel[0]. But more importantly, in the context of the article: all functions in JS are closures, regardless of how you write them. [0] https://babeljs.io/docs/en/babel-plugin-transform-arrow-functions https://babeljs.io/docs/en/babel-plugin-transform-arrow-func...
- FranceBacce 6y agoComing from writing JS all day, I've recently started learning Rust and I find it extremely refreshing. Yes, it is heavy, low level and forces you to think about how things work under the hood, but it empowers me to do high performance stuff I just coulnd't do in JS before. Def recommend it as a second / third language
- crazypython 6y agoIn response to this post, I wrote "Dlang for JavaScript Developers." Here it is: https://news.ycombinator.com/item?id=23747652 https://news.ycombinator.com/item?id=23747652
- giancarlostoro 6y agoThanks for that, I don't do enough D to know some of these syntatic features but I do love the language. I do plenty of JS so this overview is spot on for me.
- Jysix 6y agoGood, now i need the same thing but for async :)