Lambda in data class constructor params

I’m having a discussion with my team, trying to convince them not to use lambdas in a data class.
data class Data(val name: String, val callback: () -> Unit)

There are no recommendations in the documentation whether it is a valid use case.
My understanding is that a lambda introduces more then 1 issue into such a data class:

  • lambda is not comparable by value, so having a generated equals and hashcode does not add any value.
  • lambda may wrap mutable refs
  • passing around such a data class may cause memory leakage
  • a data class with lambda is not serialisable.

I would like to receive some opinions from the community. Why are lambdas allowed as parameters of a data class, and not checked as an issue by the compiler?

1 Like

It is not unusual to create classes whose main purpose is to hold data.
Data classes | Kotlin Documentation

A “function” isn’t “data”.

Maybe, using a data class may be acceptable for internal use (private data class), but every exotic use case should be specifically considered and documented.

1 Like

Exotic is the word. Using a data class that does not contain just data looks like a class but not a data class anymore. This is my point.
So maybe it should be warned at compile time as a confusing way of use a data class.

Specifically with a Composable, now it seems to be trendy to put lambdas as constructor params into a data class which is supposed to be a state class.

It would be nice if this kind of usage was documented against to avoid misuse.

Interesting topic and I don’t know if there’s a clear answer in general. I’ve changed my mind a couple of times already in the last 5 minutes and I’m sure I’ll change it again. :smile:

My current thinking:

lambda is not comparable by value, so having a generated equals and hashcode does not add any value.

This is true for lambdas, but strictly speaking val callback: () -> Unit is a reference to a function: this can be a lambda or a function (::foo). It is possible (and potentially meaningful) to compare references to functions, perform introspection, etc.

lambda may wrap mutable refs
passing around such a data class may cause memory leakage

While true, at the same time, data class field can refer to an instance of a class that is also mutable.

Also, we can just store the reference to the functions/mutable instances without running/mutating them. Should this be allowed?

If we banned function references in data classes, should Kotlin restrict data classes to using PODs and other data classes? Should Thread, Runnable or even File be banned from data classes? :thinking:

a data class with lambda is not serialisable

They might or they might not be, depending on the runtime modules. Just my personal view, but I’m not a fan of Java/Kotlin serialisation when magic happens at runtime. My personal preference would be having serialisable types that are checked at compile time. Data classes could inherit serialisation if and only if all their fields are serialisable (e.g. like in Rust). Anyway, offtopic.

In general, I think it is useful to be able to reference functions in data classes, especially when they’re not called. When calling them, there’s a very blurry line between a general class and a function and there’s probably no way to detect misuse at compile time. Documentation is probably the only option.

1 Like

If a data class holds something else then data, say Thread or Runnable or Lambda, what benefit does data class give over a class?
In an app usually there are classes, that manipulate data.

When dependency is reversed, and now data manipulates class, there is something perverted about such an approach imho.

To be honest, I don’t understand most of your points about mutability, serialization, etc. Data classes are not required to be immutable, are not required to be serializable and I don’t see how it may cause memory leaks (at least comparing to regular classes).

But for me it also feels strange to use data classes to hold something which is clearly not data. It may be misleading to others and we don’t benefit too much from auto-generated hashCode(), equals(), which is usually the main point of using data classes. Please note we still partially benefit from generated toString(), copy() and componentN(), but at least for me cons outweigh pros and a regular class makes more sense for me.

But similarly to @kpc , I don’t have a strong opinion on this. Having toString() and copy() is still pretty nice if we have a class with 5 props and one of them is lambda.

1 Like

It is as well possible to declare everything as a data class.

The data class holds data to make an obvious separation between logic and data, but also to use auto generated benefit sometimes.

Although I admit that data class is not required to be immutable, it is quite handny and a good practice when they are - I usually require them to be immutable.