Kirill Grouchnikov
08/24/2026, 8:08 PMKirill Grouchnikov
08/24/2026, 8:08 PMopen class BaseBuilder<out B: BaseBuilder<B>> {
protected var _foo: Int = 0
fun foo(foo: Int): B {
this._foo = foo
return this as B
}
}
class Builder: BaseBuilder<Builder>() {
protected var _bar: Int = 0
fun bar(bar: Int): Builder {
this._bar = bar
return this
}
}
fun main() {
Builder().bar(1).foo(2)
Builder().foo(1).bar(2)
}Kirill Grouchnikov
08/24/2026, 8:08 PMreturn this as B in the BaseBuilder.foo, the compiler says Unchecked cast of 'BaseBuilder<B (of class BaseBuilder<out B : BaseBuilder<B>>)>' to 'B (of class BaseBuilder<out B : BaseBuilder<B>>)'.Kirill Grouchnikov
08/24/2026, 8:10 PMas B, the compiler says that there is a return type mismatch. And if I change the foo method in the BaseBuilder to return BaseBuilder<B> and that function to return this, then the chain of Builder().foo(1).bar(2) does not compile because bar is not seen on the BaseBuilderKirill Grouchnikov
08/24/2026, 8:11 PMCLOVIS
08/24/2026, 8:12 PMfoo and bar ?Kirill Grouchnikov
08/24/2026, 8:13 PMCLOVIS
08/24/2026, 8:13 PMKirill Grouchnikov
08/24/2026, 8:14 PMKirill Grouchnikov
08/24/2026, 8:14 PMadd methods come from different levels in the builder class hierarchyCLOVIS
08/24/2026, 8:15 PMKirill Grouchnikov
08/24/2026, 8:15 PMKirill Grouchnikov
08/24/2026, 8:15 PMCLOVIS
08/24/2026, 8:18 PMopen class BaseBuilder {
protected var _foo: Int = 0
fun foo(foo: Int) {
_foo = foo
}
}
class ABuilder : BaseBuilder() {
protected var _bar: Int = 0
fun bar(bar: Int) {
_bar = bar
}
}
fun buildBase(block: BaseBuilder.() -> Unit): Base =
…
fun buildA(block: ABuilder.() -> Unit): A =
…
Using your example, it would look like
buttonBar {
button(newButton)
relatedGap()
button(editButton)
relatedGap()
button(deleteButton)
}CLOVIS
08/24/2026, 8:18 PMCLOVIS
08/24/2026, 8:22 PMCLOVIS
08/24/2026, 8:23 PMKirill Grouchnikov
08/24/2026, 8:29 PMCLOVIS
08/24/2026, 8:43 PMCLOVIS
08/24/2026, 8:43 PMCLOVIS
08/24/2026, 8:44 PMYoussef Shoaib [MOD]
08/24/2026, 10:13 PMclass FakeBuilder: BaseBuilder<Builder>()
Which gives you a CCE at runtime.
Of course, if you know that your implementations are reasonable, then it's fine.
The DSL approach is way better thoughtKirill Grouchnikov
08/24/2026, 10:16 PMKirill Grouchnikov
08/24/2026, 10:17 PMpadding call to be at the top level and not as a DSL function, since it doesn't make sense to call it more than once. Even though in the original Java implementation it's at the level of the fluent API which was available to call more than once as well.Kirill Grouchnikov
08/24/2026, 10:20 PMmodifierYoussef Shoaib [MOD]
08/24/2026, 11:33 PMCLOVIS
08/25/2026, 7:38 AMButtonStackBuilder(…) {
for (button in listOf("Start", "Pause", "Stop")) {
button(button)
unrelatedGap()
}
}
This would be much more difficult with a builder!