What Kotlin could learn from Rust « Otaku – Cedric’s blog

This is a continuation of my previous article, where I explored some aspects of Kotlin that Rust could have learned from. This time, I’m going to look at some traits that I really enjoy in Rust and that I wish Kotlin would adopt.

Before we begin, I want to reiterate that my point is not to start a language war between the two languages, nor am I trying to turn one language into another. I spent a lot of time analyzing which features I want to discuss and automatically removed features that made perfect sense for one language and would be absurd in the other language. For example, it would be foolish to ask for garbage collection at Rust (since its main proposition is very tight control over memory allocation) and mutually, it would not make sense for Kotlin to adopt a shave tester, since the fact that Kotlin is garbage collected is one of its main appeals.

The features I covered in my first article and this article are functionality that I think can be adopted by any of the languages ​​without jeopardizing their main design philosophies, although since I am not familiar with the internal features of any of the languages, I may be able to stop with some of the languages. These, and I welcome feedback and corrections.

Let’s dig.

Macro

I have always had a love-hate relationship with macros in languages, especially unhygienic ones. At the very least, macros must be fully integrated into the language, which requires two conditions:

  • The compiler should be aware of macros (unlike for example the pre-processor in C and C ++).
  • The macro should have full access to a statically typed AST and be able to modify that AST safely.

Rust macros meet both of these requirements and as a result, open up a set of very interesting capabilities, which I’m pretty sure we’re just starting to explore.

For example, e dbg!() Macro:

let a = 2;
let b = 3;
dbg!(a + b);

Will print

[srcmain.rs:158] a + b = 5

Note: Not only the source file and the line number but the full expression displayed (“a + b”).

Another excellent example of the power of macros can be seen in debug_plotter A box that allows you to draw variables:

fn main() 
    for a in 0..10 
        let b = (a as f32 / 2.0).sin() * 10.0;
        let c = 5 - (a as i32);

        debug_plotter::plot!(a, b, c; caption = "My Plot");
    

How beautiful and nerdy is that?

Kotlin is not fully armed in this class because the combination of notes and note processors provide a set of functionality that is not very far from what you can do in Rust with macros and features. The main difference is that while Kotlin’s approach only allows Kotlin’s valid code to ever exist in Kotlin’s source file, Rust allows any arbitrary syntax to appear as a macro argument, and it depends on the macro to create the correct Rust that the compiler will get.

I must admit that my opinion does not entirely depend on this specific aspect.

On the one hand, it’s nice to be able to write any type of code in a Rust source file (this’s what React does with JSX), on the other hand, the potential for abuse is high and one can rightly fear from a day when the rust source file will not look like Rust code. However, so far, my fear has never materialized and most of the macros I have encountered use custom syntax in a very economical way.

Another very important aspect of macros is that Rust IDEs understand them (well, at least, CLion does, and maybe all IDEs can and will) and they will immediately show you errors when something goes wrong.

Macros are used in a very large variety of scenarios and provide Rust with some really neat DSL capabilities (e.g. for libraries that support SQL, web, graphics, etc …).

Also, macros integrate very neatly with …

Preliminary processing features

Features are Rust’s version of the comments and they start with both # or #!:

#![crate_type = "lib"]

#[test]
fn test_foo() 

Nothing groundbreaking here, but what I want to discuss is this Conditional compilation aspect.

Conditional compression is achieved in Rust by combining features and macros with cfg, Which is available both as a feature and as a macro.

The macro version allows you to assemble a sentence or phrase conditionally:

#[cfg(target_os = "macos")]
fn macos_only() 

In the code above, the function macos_only() Compilation will only be performed if the operating system is macOS.

Macro version of cfg() Allows you to add additional logic to the condition:

let machine_kind = if cfg!(unix) 
    "unix"
 else  … 

At Risk of Repetition: The above code is a macro, which means it is evaluated at compile time. Any part of the condition that is not intended will be completely ignored by the compiler.

You may rightly wonder if such a feature is needed in Kotlin, and I asked myself the same question.

Rust compiles to original executables, on multiple operating systems, making this type of compilation almost conditional on the requirement if you want to publish objects on multiple purposes. Kotlin does not have this problem because it produces neutral operating system JVM-enabled operating system files.

Although Java and Kotlin developers have learned to do without a preprocessor since the preprocessor C left such a bad impression on almost everyone who used it, there have been situations in my career where the ability to maintain a conditional compilation that includes or does not include a file source, or even just statements , Expressions or functions, were useful.

No matter where you stand in this debate, I must say that I very much enjoy how two very different traits in the rust ecosystem, macros and traits, are able to work together to produce such a useful and versatile trait.

Expansion features

Expansion features allow you to make the structure fit into an “after-the-fact” feature, even if you do not own any of them. Repeat this last point: it does not matter if the structure or attribute belongs to the directories you did not write. You can still make this structure fit this feature.

For example, if we want to apply a last_digit() Function on type u8:

trait LastDigit 
    fn last_digit(&self) -> u8;


impl LastDigit for u8 
    fn last_digit(&self) -> u8 
        self % 10
    


fn main() 
    println!("Last digit for 123: ", 123.last_digit());
    // prints “3”

I may be biased about this feature because unless I’m wrong, I was the first person to offer similar functionality for Kotlin back in 2016 (Link to discussion).

First of all, I find the Rust syntax elegant and minimalist (even better than Haskell’s and without a doubt, better than the one I suggested for Kotlin). Second, the ability to expand features in this way opens up a lot of expansion and power on how to model problems, but I’m not going to dive too far into this topic because it will take too long (look for “type classes” to get a sense of what you can achieve).

This approach also allows Rust to mimic Kotlin’s expansion functions while providing a more general mechanism for expanding not only functions but also types, at the expense of slightly more literal syntax.

In short, you have the following matrix:

Kotlin rust
Extension function fun Type.function() ... Extension feature
Extension feature No Extension feature

Charger

This probably comes as a surprise since with Gradle, Kotlin has a very strong structure and package manager. Both tools have exactly the same functional surface area, allowing you to build complex projects while managing library downloads and dependency resolution.

The reason why I think cargo It is a better alternative to Gradle is because of its clean separation between the declarative syntax and its imperative. In short, standard and common construction guidelines are detailed in the statement cargo.toml File in ad hoc time, more programmatic construction steps are written directly in Rust in a file called build.rs, Using the Rust Code calls the API for fairly easy construction.

Gardel, on the other hand, is a mess. First because it started to be defined in Groovy and it now supports Kotlin as the language of construction (and this transition is still ongoing, years after it started), but also because the documentation of both is still incredibly bad

By “bad”, I do not mean “missing”: there is a lot of documentation, it is simple … bad, overwhelming, mostly outdated, or obsolete, etc … Requires hundreds of lines to copy / paste from StackOverflow as soon as you Need something off the trail. The plugin system is set up very loosely and basically allows all plugins to access whatever they feel like inside the Gradle internal structures.

Obviously, I’m quite opinionated on this subject because I created Gradle-inspired building tools but use more modern approaches to syntax and plug-in resolution (This is called cobalt), But regardless of that, I think cargo Manages to strike a very delicate balance between a flexible tool and a construction + dependency manager that adequately covers the entire default configuration without being overwhelmingly complex once your project grows.

U8, U16,…

In Rust, the types of numbers are quite simple: u8 Is an integer without a mark of 8 bits, i16 Is an integer with a 16-bit mark, f32 Is a 32-bit float etc …

This is a fresh breeze for me. Until I started using these types, I never recognized how uncomfortable I always am with the way C, C ++, Java, etc. define these types. Whenever I needed a number, I would use it int or Long By default. In C, sometimes I went up long long Without really understanding the consequences.

Rust forces me to pay close attention to all of these types and then, the compiler will keep me relentless whenever I try to perform casts that can lead to bugs. I really think all modern languages ​​should obey this convention.

Reporter error messages

Not to say that Kotlin’s error messages are bad, but Rust has certainly set a new standard here, in a number of dimensions.

In short, here’s what you can expect from the Rust compiler:

  • ASCII graphics with arrows, colors, clear demarcation of problematic sections.
  • Simple English and detailed error messages.
  • Suggestions on how you can fix the problem.
  • Links to relevant documentation where you can find out more about the problem.

I definitely hope future languages ​​get inspired.

portability

About twenty-five years ago, when Java came out, the JVM promised: “Write once, run everywhere” (“WORA”).

While this promise stood on a shaky foundation in the early years, there is no denying that WORA is a reality today, and has been for several decades. Not only can JVM code be written once and run anywhere, such code can also be written anywhere, which represents an important boost to developer productivity. You can write your code in any of Windows, macOS, Linux and deploy in any of Windows, macOS and Linux.

Surprisingly, Rust is also capable of such flexibility, even though it produces original executables. Regardless of the operating system in which you write your code, producing executable files for a large number of things is trivial, with the added benefit that these executable files are genuine, and thanks to the amazing technical achievement that the LLVM is also high-performance.

Before Rust, I came to terms with the fact that if I wanted to run on multiple operating systems, I had to pay the price of running on a virtual machine, but Rust now shows that you can have your cake and also eat it.

Kotlin (and the JVM in general) are also starting to learn this lesson, with initiatives like GraalVM, but producing executable files for the JVM code still involves restrictions and limitations.

Finishing

I have a lot more to say about all this.

And by “all this,” I mean “rust and cutlin.”

Both are such interesting languages. I love both, but for different reasons. I hope I was able to convey some of my affection in these two posts. While these articles may seem critical, they are truly love letters. I am a very demanding developer, someone who has been writing code for forty years and who plans to continue to do so for as long as his mental abilities allow. I feel an unreasonable passion for programming languages, and I hope my passion shone through these two articles.

TestNG Is a project I started around 2004 with the sole intention of mixing things up. I wanted to show the Java world that we can do better than JUnit. I had no intention of anyone liking and embracing TestNG: it was a project lab. trial. All I wanted to do was show we could do better. I really hoped the JUnit team (or whatever was left of it) would look at TestNG and think “wow, I never thought of that! We can integrate these ideas into JUnit and improve it even more!”.

That’s my goal with these two posts. I would be excited if these two very, very different worlds (the Rust and Kotlin communities) would stop for a second from their incredible pace of development, look at each other, even though they really had no interest in doing so, and understand “well … it’s interesting … I wonder if we can Do it?”.

Discussions on reddit:

This entry was posted on November 9, 2021, at 3:58 pm and is filed under Unclassified. You can follow any responses to this entry through the RSS 2.0 feed. You can Leave a comment, Or tracking from your site.

Source

spot_img

LEAVE A REPLY

Please enter your comment!
Please enter your name here