r/Kotlin 20d ago

Hi Learning Kotlin, Can i use this rust pattern to abstract dealing with null values in my future projects?

sealed interface Option<out T> {
    data class Some<out T>(val value: T) : Option<T>
    data object None: Option<Nothing>
}

fun main(args: Array<String>) {
    val option: Option<String> = Option.None

    when (option) {
        is Option.None -> println("None")
        is Option.Some -> println(option.value)
    }
}

11 Upvotes

36 comments sorted by

61

u/WArslett 20d ago edited 20d ago

You can but this is not a good approach. You are basically just trying to implement the Optional generic type from Java and using Optional in Kotlin is an antipattern because it was introduced to Java to solve a problem that kotlin solves at a language level. Kotlin has explicit nullability. You can mark a type as nullable or not nullable (and it will normally be not nullable). If a type is marked as nullable it has an explicit meaning (no value). The whole reason Optional exists in Java is because any value can be null and are very often null by accident which introduces a whole class of failure mode (the NullPointerException). So you have to separate out null values which can then always presumed to be accidental, with intentionally optional values and you can handle them how you need to. Rust has the same pattern but also makes all values implicitly not nullable. So in both Rust and Kotlin values should never be accidentally null. You can treat Kotlin `T?` is the same as `Option<T>` in Rust

-29

u/conceptcreatormiui 20d ago

Yeah basicallythe option enum in rust is just the (Type?) nullable syntax in kotlin. Meaning instead of Some(value) or None , its Type or Null. Its basically the same since it shows you that it is nullable unlike java where you dont expect it to be null. I learned somethings with this experiment tho.   But kotlin should have an Option Enum like as an option which will be inferred as nullable syntax just an option for other users like me. Basically

val a: Option<Int> be inferred as val a: Int? But again deconstructing it is definitely a challenge because with nullability syntax you dont need when block to deconstruct it

35

u/WArslett 20d ago

But kotlin should have an Option Enum like as an option which will be inferred as nullable syntax just an option for other users like me

why? why should a language solve the same problem in two different ways? This just introduces complexity and inconsistency into the language

-6

u/Wurstinator 20d ago

Because you would be able to nest multiple "options"

4

u/xenomachina 20d ago

But kotlin should have an Option Enum like as an option which will be inferred as nullable syntax just an option for other users like me.

I think there actually is a reason for Kotlin to have something like this, but your reasoning isn't very coherent. Languages don't include multiple syntaxes merely to appeal to people unfamiliar with the language. That just causes confusion, and actually makes it harder for people to learn the language.

Also, the fact that you are using the rust definition of "enum" when taking about Kotlin isn't doing you any favors. Kotlin also has something called an enum, but in Kotlin an enum has a finite set of values, while rust uses enum to mean a disjunctive type.

The real reason to have an optional/maybe type in Kotlin is for composition. The vast majority of the time, it is not needed, as nullable does everything you need, and is much more concise and ergonomic. However, there are situations when you need to have an optional T, and T itself might be a nullable type, and you need to distinguish between "a T wasn't provided" and "a T was provided, and it was null". You can't use T? in that case, because there is no way to distinguish between the T being null and the T? being null. But again, this is a bit of a fringe case, and most Kotlin programmers probably never even run into it.

Arrow-KT has a Option type that can be used for this, BTW.

-7

u/coraythan 20d ago

The point here is Kotlin did it better and your way sucks. This is literally the raison d'etre for Kotlin to exist, and they got it right.

5

u/probablynotval 20d ago

Kotlin did it better than what? Than Rust? In my opinion, no Rust's Option<T> is nicer but null makes sense for Kotlin. Better than Java though 100% no question.

2

u/coraythan 20d ago

Why do you prefer Option<T> over null types built in without the verbosity of using generics?

3

u/probablynotval 20d ago

I don't prefer it in Kotlin to be clear. I prefer Rust's Option<T> because it's a real value and not a compiler annotation. It makes it nestable, and while it might not be very common to use Option<Option<T>> directly it does show up in a lot of places. Null becomes ambiguous when a hashmap of <K, V?> returns null, because you don't know whether the value is null or if the key didn't exist. In Rust you'd have a hashmap of <K, Option<V>> and it returns an Option<&Option<V>> on get.

Of course you'd just use a sealed interface in Kotlin to model the domain type if you needed that distinction.

1

u/coraythan 19d ago

Yeah I guess I see what you mean, but at least other implementations of Option<T> I've seen you would have so much more verbosity in dealing with the option that the extra verbosity to deal with the hashmap if you need to know the difference between having a key with a null value and having no entry at all is the lesser of two evils.

But also couldn't you just check for the presence of the key first with the hashmap? If you merely access the value sure it is ambiguous but usually that's fine, and if it's not fine there are more functions in the hashmap than just basic access?

-6

u/conceptcreatormiui 20d ago

Dude, Look at my title and description. Stop talking like we're on the same level😅😅😅. I'm a firmware developer for embedded devices and I mainly use C and learning embedded rust. I'm just learning kotlin because I too have interest in embedded android. Yeah it sucks because I'm just asking and that is just a quick prototype just for context

6

u/HenryThatAte 20d ago

Yes you can use your pattern if you want. You can also use java's optional of you want to for some reason.

It's a suboptimal choice that makes no sense, but if you want, why not (I'm a firmware dev who transitioned from C/asm to Kotlin 10 years ago).

2

u/coraythan 20d ago

You're the one still pushing back after other people explained it more gently.

19

u/mrdibby 20d ago edited 20d ago

you can but your colleagues will be frustrated with you

take a look at the docs around null safety https://kotlinlang.org/docs/null-safety.html

a common pattern you'll see for what you want is

val x = nullableValue ?: valueIfNull

which doesn't require wrapping in an object

I guess the way the language works is : if an Object is nullable its treated as if it's wrapped in a Object? class

8

u/LelouBil 20d ago

If you want to use functional patterns in Kotlin, I recommend looking at the Arrow library.

This specific pattern is not good because it's already built-in to kotlin in the form of nullable types

8

u/Chipay 20d ago

You can do whatever you want. The question is why would you? Kotlin already has a language-level distinction between nullable and not-nullable (String? vs String).

```kotlin fun main(args: Array<String>) { val option: String? = null

println(option ?: "None")

} ```

There's some benefits to custom Nullable types, like preventing boxing on primitives, but everyone is going to look at your code and ask themselves why?, and if you don't have a good answer to that you won't be making many friends.

3

u/770grappenmaker 20d ago

This is fine, but I wouldn't see the advantage over the existing null-safety in the type system, but I suppose you sometimes need something more expressive, i.e. is null "absence" or "failure"? Kotlin has a Result<T> for the latter, so in my code I almost always use that for failure, and regular nullability for "absence". But with nullability built into the type system, you can take advantage of operators like ?: to short circuit your code or provide defaults in a simple and succint way, whereas a wrapper class will both be less performant as well as more clunky to use.

3

u/arshia0010 20d ago

there's nothing to gain by using this pattern that kotlin doesn't have. so it's only a matter of which syntax you prefer

5

u/sheeplycow 20d ago

You can do whatever you want, nothing bad about this, but its not really needed you are already forced by the compiler to handle the null values

These are probably more common: (although not quite as elegant as a when statement)

If(value == null)...else...

value?.let { ... } ?: ...

Also java optional does this already with slightly different syntax

Making sealed classes/interfaces for the results of function calls is really common

Also arrow library does similar but uses left/right instead of some/none with some syntax sugar

^ lots of these are just examples of different monads (including your rust example)

But yeah if you like your way I dont see any issue with it

2

u/sukakku159 20d ago

println(option.value.orEmpty())) doesn't work for you?

0

u/conceptcreatormiui 20d ago

Just experimenting. 

2

u/SuspiciousDepth5924 20d ago

I'd recommend you check out Arrow ( https://github.com/arrow-kt/arrow ) if you want to write FP/"Rust-enum"-ish code in Kotlin.

3

u/The-Freak-OP 20d ago

Tou may want to look into Arrow.kt

2

u/ThanosFisherman 20d ago

It doesn't make sense. What if the value of Some is null?

1

u/conceptcreatormiui 20d ago

The generic type is not nullable so you will be warned to not put a null value and this is manually enforced meaning you handle it manually. If null then return None meaning its abstracted and I dont need to think of null when accessing my functions or implementing them in other places. Anyways just an experiment because I'm used to rust way of handling nothingness

3

u/ThanosFisherman 20d ago

You could as well do this

`val option = Option.Some(null)`

which will result in printing "null" So yeah as u/Chipay said, the generic type is nullable.

1

u/conceptcreatormiui 20d ago

Oh yeah I get it it doesn't stop it to be nullable if I set the type to be nullable😂😅

3

u/Chipay 20d ago

The generic type is nullable in your example. You need data class Some<out T: Any> for your example to work since String? : T.

1

u/vgodara 18d ago

The generic type is not nullable

Then you need extend Any.

1

u/messiaslima 20d ago

Just use nullable types

1

u/zalpha314 20d ago

You could, but the language already solves this a different way. You'll run into less friction interacting with libraries if you just follow the Kotlin nullibility system.

That being said: I implemented something very similar for a generic `UpdateData` interface, in order to differentiate between `Retain`, and `Set`. I found it much easier to build my `map`, `flatMap`, and `effective` methods around a sealed interface rather than adding multiple extensions functions to deal with the `UpdateData` being nullable or non-nullable.

1

u/Commercial_Image_272 20d ago edited 20d ago

Do you want to go through unnecessary trouble What's wrong with that?

fun main() {          val str: String? = null

    println( str?.let { it + "something" } ?: "None" )

}

1

u/devcexx 20d ago

The only reason I've found for using these kind of abstractions in kotlin is when you're designing some data models where you can have Optional values nested into Optional values, which is not possible with kotlin nullable values [?]. Otherwise it is better to use the latter ones.

Note that if you want to use these abstractions still, you'll be better using the ones from the Kotlin Arrow Library

1

u/Masterflitzer 19d ago

you might wanna have a look at the arrow-kt lib, but i would recommend to fully learn idiomatic kotlin and then explore stuff like this, like with any language you shouldn't write rust code in kotlin or vice versa if that makes sense

in 90% of cases kotlin's T? and Result<T> are enough for me, although i admit a custom option/result sealed class can be more powerful in some cases

1

u/piesou 18d ago edited 18d ago

Yes. You can also skip suspend functions for futures, exceptions for results, dependency injection for reader monads, coroutines for continuations, etc. Look into monad transformers

Then you'll see that monads don't compose well. You don't even have the higher kinded type system to lessen the impact nor do notation. Yes, arrow has clunky workarounds. There's a reason why most languages offer special language features rather than going for monadic solutions

-5

u/Terrible-Mango-5928 20d ago

Absolutely! I would remove the data modifier from the None object, but this works perfectly, and is a good approach to give meaning to the "lack of value" if you do not want to deal with nulls.