Comment by well_ackshually
1 hour ago
>A DSL needs it clear the minimum bar of “why is this not just using a real programming language?”.
No, a DSL only needs to be better than the language itself to write a layout in. UI is a whole different beast than anything else, where deep nesting is common, layouts flow in many ways, and more. Rust is simply not appropriate to make that pleasant.
Take a simple layout like you'd declare it in Kotlin using Compose:
@Composable
fun GreetingScreen(title: String, onBack: () -> Unit) {
var count by remember { mutableStateOf(0) }
val scope = rememberCoroutineScope()
Column(Modifier.fillMaxSize()) {
Surface(Modifier.weight(1f)) {
Text("Count: $count", style = TextStyles.h1)
}
Button(onClick = onBack) {
Button(onClick = { count++ }) { Text("+1") }
Button(onClick = {
scope.launch {
delay(1000)
count++
}
}) { Text("+1 later") }
}
}
}
Kotlin naturally has trailing lambdas, method references, has the advantage of being a GC'd language so memory management is out the window.
In pure rust ?
pub fn counter_screen(ui: &mut Composer, on_back: Rc<dyn Fn()>) {
let count = remember(ui, || 0_i32);
column(ui, Modifier::new().fill_max_size(), {
let count = count.clone();
let on_back = Rc::clone(&on_back);
move |ui, col| {
surface(ui, Modifier::new().weight(col, 1.0), {
let count = count.clone();
move |ui| {
let label = format!("Count: {}", *count.lock().unwrap());
text(ui, TextProps {
value: &label,
style: Some(&TextStyles::H1),
..Default::default()
});
}
});
row(ui, Modifier::new(), {
let count = count.clone();
move |ui, _row| {
button(ui, ButtonProps {
on_click: {
let count = count.clone();
move || count.update(|n| *n += 1)
},
enabled: true,
modifier: Modifier::new(),
}, |ui| text(ui, TextProps { value: "+1", ..Default::default() }));
button(ui, ButtonProps {
on_click: {
let count = count.clone();
move || {
let count = count.clone();
spawn(async move {
sleep(Duration::from_secs(1)).await;
count.update(|n| *n += 1);
});
}
},
enabled: true,
modifier: Modifier::new(),
}, |ui| text(ui, TextProps { value: "+1 later", ..Default::default() }));
}
});
button(ui, ButtonProps {
on_click: { let on_back = Rc::clone(&on_back); move || on_back() },
enabled: true,
modifier: Modifier::new(),
}, |ui| text(ui, TextProps { value: "Go back", ..Default::default() }));
}
});}
What a delicious piece of trash. Since you've said no macros, you're carrying an additional variable everywhere (Composer is a Compose system, but you have the same thing in every UI library that does the same. Call it Context, whatever you want). You have shitty lambdas that need to pass the scope too (because Modifier.weight() only works on a ColumnScope). You have no named parameters, and the only way you would would be crap like having a ButtonProps struct. You have impl Fn() + 'static written in your code. And then if you start to want to do some actual work like "clicking on the button increases a counter", you're suddenly spamming Arc::clone, |move| Rc's everywhere in your code because Rust is simply not a good language for UIs.
(And if you want to go the old school way of simply managing yourself a widget tree, good luck, not only is it always full of bugs, it's also an unmaintainable garbage fire.)
Clojure is not a good language for DSLs either, and the post you've linked is just jerking itself off saying "look i can convert some clojure into the exact bash equivalent by only changing one thing" when it's the exact reason why every single clojure project ends up reimplementing the wheel twelves times and is a piece of shit unmaintainable kludge of whatever code the local genius wanted to commit in this morning. Good languages have constraints.
So yes: DSLs _always_ offer a better alternative. QML is a good DSL, and using Javascript is not the reason why.
No comments yet
Contribute on Hacker News ↗