Bikeshedding the gnome-class mini-language
- From: Federico Mena Quintero <federico gnome org>
- To: rust-list <rust-list gnome org>
- Subject: Bikeshedding the gnome-class mini-language
- Date: Tue, 24 Oct 2017 13:58:40 -0500
Hey, all!
I'm making very good progress with the rewrite of gnome-class. I've
figured out how to port it gradually from lalrpop and the old proc-
macro scheme of things, to proc-macro2 and syn/quote.
As a reminder, gnome-class is a procedural macro that you call like
this:
gobject_gen! {
class Counter {
struct CounterPrivate {
f: Cell<u32>
}
private_init() -> CounterPrivate {
CounterPrivate {
f: Cell::new(0)
}
}
fn add(&self, x: u32) -> u32 {
let private = self.get_priv();
let v = private.f.get() + x;
private.f.set(v);
v
}
fn get(&self) -> u32 {
self.get_priv().f.get()
}
}
}
This generates all the GObject boilerplate to register a Counter class,
its private data, the #[repr(C)] functions to call the add() and get()
methods from C, etc.
However, GObject supports a lot of features (signals, properties, etc.)
and I am trying to come up with suitable syntax. I would love your
feedback on the following.
By default, the answer to many things here is "what does Vala do?" :)
This is probably often the right thing syntax-wise. We can make it
more Rust-like as needed.
Basic syntax
============
Here is a bare-bones class that derives from GObject; same as the
example above:
gobject_gen! {
class Counter {
struct CounterPrivate {
f: Cell<u32>
}
private_init() -> CounterPrivate {
CounterPrivate {
f: Cell::new(0)
}
}
fn add(&self, x: u32) -> u32 {
let private = self.get_priv();
let v = private.f.get() + x;
private.f.set(v);
v
}
fn get(&self) -> u32 {
self.get_priv().f.get()
}
}
}
You can specify the parent class like "class Foo: Bar". If it is not
specified, the code generator assumes that GObject is your parent class.
"struct CounterPrivate" is your private struct. Each class must have
one and only one such structures.
private_init() is a mandatory function that gets called during
initialization; you return an initialized private struct, and *that*
becomes the initial value of what gets put in the
g_type_class_add_private() chunk.
As an alternative, I want to make it possible to specify
#[derive(Default)] for your private struct, and thus not having to
specify a private_init():
gobject_gen! {
class Counter {
#[derive(Default)] // <- note this thing
struct CounterPrivate {
f: Cell<u32>
}
}
}
Constructors
============
Vala supports a construct{} block, which gets called after all (construct-time?)
properties have been set. It uses GObjectClass::constructor. I haven't decided
on this yet, nor on how the following from Vala would work:
public MyConstructor (int a, int b, int c) {
Object (some_construct_only_prop: a, some_other_prop: b); // construct superclass
this.my_own_prop = c;
}
Virtual vs. non-virtual methods
===============================
Virtual methods have a slot in FooClass; non-virtual ones don't. They
are just C functions that happen to take a &self as the first
parameter.
Syntax-wise, we need a way to specify virtual / non-virtual (default
to non-virtual, like C# does), and a way to override virtual methods
from your parent class. Some possibilities:
gobject_gen! {
class Flarp: SuperClass {
// ... private struct foo
// static method
fn florp(&self, x: i32) -> bool {
// ... code
}
// virtual method, no default handler
virtual fn virtual_method_1(&self, x: i32) -> bool;
// virtual method with default handler
virtual fn virtual_method_2(&self, y: i32) {
// ... code
}
override fn foo(&self, z: i32) -> Bar {
// do something with the superclass
}
}
}
For the public C API, these would generate the following prototypes in a
header file:
gboolean flarp_florp (Flarp *self, gint x);
gboolean flarp_virtual_method_1 (Flarp *self, gint x);
void flarp_virtual_method_2 (Flarp *self, gint y);
For the overriden method "foo", there presumably is a superclass_foo()
somewhere.
Problem: how would the code generator know which superclass struct
has the .foo field in order to override it? Should we specify this
overriden method like
override fn SuperClassName::foo(&self, ...);
?
Visibility: the code generator assumes that all the "fn" that you put
inside a class are public. Should we instead allow "fn" and "pub fn"?
Should virtual methods be declared as "virtual pub fn" correspondingly?
Implementing interfaces
=======================
You specify which interfaces you implement like this:
class Foo: Superclass, Iface1, Iface2 {
// ...
}
Now, how to specify the implementations?
First alternative, similar to our "override" above:
class Foo: Superclass, Iface1, Iface2 {
override fn Iface1::method_1(&self, ...) {
// code
}
override fn Iface2::method_2(&self, ...) {
// code
}
}
Second alternative, more Rust-like, with "impl" items inside the class:
gnome_class! {
class Foo: Superclass, Iface1, Iface2 {
impl Iface1 for Foo {
fn method_1(&self, ...) {
// code
}
}
impl Iface2 for Foo {
fn method_2(&self, ...) {
// code
}
}
}
}
Third alternative, also Rust-like, with "impl" items outside the class:
gnome_class! {
class Foo: Superclass, Iface1, Iface2 {
// ...
}
impl Iface1 for Foo {
fn method_1(&self, ...) {
// code
}
}
impl Iface2 for Foo {
fn method_2(&self, ...) {
// code
}
}
}
Creating new interfaces
=======================
I haven't decided this yet. Maybe something like
gnome_class! {
interface Foo {
virtual fn blah(&self, x: i32) -> bool;
virtual fn bleh(&self, y: *const c_char);
}
}
I.e. make it similar to "trait", except that the "virtual" keywords
are there to remind you that you are not in Rust land?
That would generate the appropriate #[repr(C)] structs so that
people can implement those interfaces later.
Signals
=======
Vala specifies signal flags like this:
[Signal (action=true, detailed=true, run=true, no_recurse=true, no_hooks=true)]
public signal void sig_1 ();
I think we can use function attributes:
// no default handler, no return value
signal no_default_handler(&self, x: u32);
// with default handler, with return value
signal with_default_handler(&self, e: &EventButton) -> bool {
// default handler code here
}
// signal flags; pick the ones you need
#[signal(run_first, run_last, run_cleanup, no_recurse, detailed, action, no_hooks, must_collect,
deprecated)]
signal foo(&self);
I haven't though of how to specify signal accumulators, or if we even
need them. Is there a non-obscure case that needs them?
Properties
==========
I took some notes about this, but they are far from final. Vala has
good examples on what the syntax may look like; it borrows from C#,
which is nice.
Feedback is appreciated!
Federico
[Date Prev][
Date Next] [Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]