Mastodon

What Does the [NoWrapper] Attribute in Vala Mean?

Vala is one of my favorite programming languages for getting stuff done. It’s a primarily object-oriented language that looks an aweful lot like C# if you squint hard enough and, more interestingly, compiles to C using the GLib Object System (more commonly referred to as GObject). This allows for the existence of many high-level features and niceties without the performance hit of running on a virtual machine. Additionally, by virtue of using C as an intermediate representation, Vala programs can leverage the entirety of the libraries within the C ecosystem (as well as the GObject-based and even C++ with some work). Bindings to these libraries exist in the form VAPI files, which, as one could probably guess, define an API for the library in Vala, and describe to the compiler how to translate the Vala semantics into C, through the use of attributes.

Attributes are described in chapter 15 of the Vala reference manual, and the NoWrapper attribute is specifically defined in section 12, "Other attributes".

NameApplies toDescription
NoWrappermethod

As you can see, it leaves much to be desired. Having seen it frequently enough to notice it yet with such irregularity to be unable to guess as to its usage, my curiosity finally got the best of me and I decided to take a deep dive into the guts of the Vala compiler to figure out what the heck it does.

Having no better idea of where to start, I began at the root of the compiler’s repository. Recursively grepping for "NoWrapper", I was surprised at how few results came up outside of the vendored VAPI directory. The only result of real interest came up in vala/valamethod.vala:

if (get_attribute ("NoWrapper") != null && !(is_abstract || is_virtual)) {
error = true;
Report.error (source_reference, "[NoWrapper] methods must be declared abstract or virtual");
return false;
}

So, we know that the NoWrapper attribute is only relevant to abstract or virtual methods. Abstract and virtual methods in Vala are similar to their counterparts in other languages. In Vala, all abstract methods are also virtual, so let’s focus on virtual methods (or functions). Vala’s object-oriented paradigms are implemented using the aforementioned GLib Object System. According to the GObject manual, methods of a class may be or non-virtual, public virtual, or private virtual. Public virtual methods are the preferred way to add overridable methods to a class, and are defined by a common method and a virtual function in the class structure in the C header file. A virtual function is just a pointer to a function, allowing it to be “overridden” by simply assigning the pointer to the new implementation. The common method along with a base version of the virtual function are implemented in the source file. The virtual function pointer is initialized to the address of this implementation in the object’s class_init function, allowing derived classes to re-implement it as desired. Otherwise, the virtual function pointer may be left NULL, meaning it is a “pure virtual” method which must be implemented by every derived class.

That was a lot to digest. Here’s an example of a public virtual method from the GObject manual:

/* declaration in viewer-file.h. */
#define VIEWER_TYPE_FILE viewer_file_get_type ()
G_DECLARE_DERIVABLE_TYPE (ViewerFile, viewer_file, VIEWER, FILE, GObject)
struct _ViewerFileClass
{
GObjectClass parent_class;
/* stuff */
void (*open) (ViewerFile *self,
GError **error);
/* Padding to allow adding up to 12 new virtual functions without
* breaking ABI. */
gpointer padding[12];
};
void viewer_file_open (ViewerFile *self,
GError **error);
/* implementation in viewer-file.c */
void
viewer_file_open (ViewerFile *self,
GError **error)
{
ViewerFileClass *klass;
g_return_if_fail (VIEWER_IS_FILE (self));
g_return_if_fail (error == NULL || *error == NULL);
klass = VIEWER_FILE_GET_CLASS (self);
g_return_if_fail (klass->open != NULL);
klass->open (self, error);
}

Note that access to the open() method is provided through a non-virtual method of the class called viewer_file_open(). This method “wraps” the arrow operator that calls the implementation pointed to by the (*open) function pointer in the ViewerFileClass class structure, klass.

Now let’s look at the example of a private virtual method (can_memory_map(), in the following):

/* declaration in viewer-file.h. */
struct _ViewerFileClass
{
GObjectClass parent;
/* Public virtual method as before. */
void (*open) (ViewerFile *self,
GError **error);
/* Private helper function to work out whether the file can be loaded via
* memory mapped I/O, or whether it has to be read as a stream. */
gboolean (*can_memory_map) (ViewerFile *self);
/* Padding to allow adding up to 12 new virtual functions without
* breaking ABI. */
gpointer padding[12];
};
void viewer_file_open (ViewerFile *self, GError **error);

See how there’s no non-virtual method wrapping the virtual one? This is what the NoWrapper attribute tells the compiler, that there is no corresponding non-virtual method to use. We can confirm this suspicion by inspecting the compiler’s test suite.

Here’s the definition of an interface, IFoo from the test of the NoWrapper attribute, with the attribute applied to an abstract method (remember that all abstract methods are also virtual), bar(), and a concrete class Foo which implements IFoo:

interface IFoo : Object {
[NoWrapper]
public abstract int bar ();
}
class Foo : Object, IFoo {
int bar () {
return 42;
}
}

And here’s the expected implementation of bar() in the class Foo:

static gint
foo_real_bar (IFoo* base)
{
Foo * self;
gint result = 0;
self = G_TYPE_CHECK_INSTANCE_CAST (base, TYPE_FOO, Foo);
result = 42;
return result;
}

Note that foo_real_bar() is static. Its scope is limited to the source file of Foo and thus is “private” in object-oriented parlance.

Here’s a simple test of an interface IFoo, with an abstract method named foo(), and again a concrete class Foo which implements IFoo:

IFoo {
public abstract bool foo ();
}
class Foo : IFoo {
public bool IFoo.foo () {
return true;
}
public int foo () {
return 42;
}
}
void main () {
var foo = new Foo ();
assert (((IFoo) foo).foo ());
assert (foo.foo () == 42);
}

And the expected implementation of foo in the class Foo:

struct _IFooIface {
GTypeInterface parent_iface;
gboolean (*foo) (IFoo* self);
};
// ...
gboolean
ifoo_foo (IFoo* self)
{
IFooIface* _iface_;
g_return_val_if_fail (IS_IFOO (self), FALSE);
_iface_ = IFOO_GET_INTERFACE (self);
if (_iface_->foo) {
return _iface_->foo (self);
}
return FALSE;
}
// ...
static gboolean
foo_real_ifoo_foo (IFoo* base)
{
Foo * self;
gboolean result = FALSE;
self = G_TYPE_CHECK_INSTANCE_CAST (base, TYPE_FOO, Foo);
result = TRUE;
return result;
}
// ...
static void
foo_ifoo_interface_init (IFooIface * iface,
gpointer iface_data)
{
foo_ifoo_parent_iface = g_type_interface_peek_parent (iface);
iface->foo = (gboolean (*) (IFoo*)) foo_real_ifoo_foo;
}

Note here that we have a non-static (“public”), non-virtual method ifoo_foo(), which wraps the access of the virtual function foo, which points to the “private” implementation foo_real_ifoo_foo().

I’m sure that by this point, if you haven’t read the GObject manual, you’re probably asking yourself what virtual private functions are for if they aren’t actually “private” in the same sense that Vala uses? I mean, they appear in public APIs, for crying out loud! Well, according to the manual, this pattern is often used to delegate tasks to child classes:

/* this accessor function is static: it is not exported outside of this file. */
static gboolean
viewer_file_can_memory_map (ViewerFile *self)
{
return VIEWER_FILE_GET_CLASS (self)->can_memory_map (self);
}
void
viewer_file_open (ViewerFile *self,
GError **error)
{
g_return_if_fail (VIEWER_IS_FILE (self));
g_return_if_fail (error == NULL || *error == NULL);
/*
* Try to load the file using memory mapped I/O, if the implementation of the
* class determines that is possible using its private virtual method.
*/
if (viewer_file_can_memory_map (self))
{
/* Load the file using memory mapped I/O. */
}
else
{
/* Fall back to trying to load the file using streaming I/O… */
}
}

In the interest of clarity, we should probably find a way to define methods with such an attribute as protected, but then again, virtual methods can have default implementations, so maybe that isn’t necessarily always the correct thing to do. In any case, first, I have a patch to improve some documentation to submit.

❋❋❋

Springboards and You

I’ve been thinking a lot about the state of user autonomy in modern computing lately, and have come to a pretty stark realization about a design pattern present, if not focal, to almost every popular operating system today. I don’t know if Apple were the first to invent this but they’re certainly the first to come to mind when I think about it, so for the purposes of this post I’ll be referring this pattern as the Springboard, and explain why I don’t like them.

For those of you wondering what my grudge against gymnastics is, springboards are a UI element that lays out a collection of widgets, for the purpose of showing little bits of information and, principally, launching applications. In the case of mobile operating systems, namely iOS and Android, they are the default view and typically the first thing you see upon unlocking the device they’re running on. On desktops, they are less prominent, and are usually restricted to simply launching applications, in the case of Launchpad on macOS and the App Grid of GNOME Shell. Due to this distinction, my argument more strongly applies to mobile OSes, but don’t believe that desktop environments aren’t also culpable.

An iPhone X displaying the Home Screen of iOS 14, featuring an arrangement of apps and widgets.

Springboard on iOS 14.

Now, why am I making such a fuss over such a handy little feature? Put simply, springboards are detrimental to user autonomy. They present a colorful arrangement of little tiles representing bundles of utility with no regard for the user’s intentions. The first step in accomplishing a task using a springboard is to wade through a pile of every app and widget that you don’t need before finding the app or widget that you do. Instead of empowering you to accomplish whatever task you’ve set out on, it presents a literal smörgåsbord of distractions, a tiny jab reminding you of its existence every time you see it, which could be hundreds of times over the course of a single week.

While it’s true that springboards are often user-customizable, in that you can organize and arrange widgets as you see fit, the result is static. It doesn’t actively seek to accommodate your needs in the moment. Take this in contrast to, say, a search-based launcher, like Spotlight on macOS or (shameless plug) Synapse. Upon activating it, you’re presented with a blank slate. Nothing, not even suggestions, appears until you start typing, after which results relavant to the inquiry begin to populate the drop-down. This is a model of information retrieval that respects users. It is additive, rather than subtractive.

The Pantheon Desktop Environment, featuring an unobtrusive search application, Synapse.

Synapse on a Pantheon desktop environment.

In defense of springboards, they can serve a purpose. Sometimes we’re searching for something, but we don’t have the words to describe it. Rather, we’ll know it when we see it. This is a perfect use case for springboards; lay everything out and examine each item one by one until we’ve found what we’re looking for. This is an exceptional problem, though, for which only exceptional solutions should be applied to.

❋❋❋

Directed Acyclic (Social Network) Graphs

I (begrudgingly) still have an Instagram account. You’d never know that unless you’re on Instagram, though. I (happily) have a personal website (in fact, if you’re reading this, you’re probably on it right now)! You would know that if you were to view my Instagram account. This is not an accident. As you may or may not be aware, both Instagram and this website exist as part of the world wide web, a collection of documents and resources connected by hyperlinks (or just links, as we refer to them in this century as) embedded within them. Links are one-way streets; much like Wikipedia, once you’ve gone down the rabbit hole, there’s no coming back. The only reason you can go back and forth across links in a browser is because it keeps track of the ones you’ve followed and order in which you’ve done so in its history. This information isn’t encoded anywhere in the link itself.

Since the emergence of Google and its PageRank algorithm, links have become the currency of the internet. Over the past two decades, this expression has taken on greater meaning. The proliferation of affiliate links primarily by Amazon meant that site owners could convert the Internet’s currency could be converted into fiat currency, though don’t kid yourself into believing that the anyone benefits anywhere near as much as Amazon does. In the past decade, the rise of social media sites has been propelled in part by leveraging the fundamental property of links that we observed earlier to create walled gardens; platforms that are easy to move to, but hard, if not impossible, to leave from.

While certainly not the only one, Instagram is the poster child of this behavior. Try to share a link on Instagram, to construct a gateway out of their realm, I’ll wait. As of time of writing, there’s only one way that I know of, which is to use the one link field that’s available in your bio. Naturally, any profile or post can be shared easily via a link. It wasn’t always like this, of course. Instagram leverages its position as one of the most popular social media sites on the web to force their users’ hands. “What are you going to do? Not use Instagram?” (That’s probably the answer, in all fairness.)

This brings us to the apex question of, “Why?” Why do platforms seek to cordon off their users? Why should you, as a user, care? Again if you’re reading this site, you’re probably aware that social media sites want your attention so they can serve you ads for which they’re paid handsomely, considering how little concrete evidence there is of the efficacy of online advertising in general. Of course, these sites are desperate to improve their numbers, if even by fractions of percentage points. They have also demonstrated countless times over that they have absolutely no respect for you or your privacy, and it’s much easier to track your every online movement to be analyzed for any shred of information about you to be shared with advertisers from the comfort of their own platform. This is, at best, only to the benefit of the platform owners. At worst (and more realistically), it is to the user’s detriment for a number of reasons.

Principally, this kind of “siloing” of information is antithetical to the very nature of the internet, which is meant for sharing between all who participate in it. Practically, users are primarily on these platforms to consume "content". Of course, many of these platforms can offer more content than any user could ever consume, and more importantly don’t care to consume, so the platform will try to select and present what it believes the user will be most likely “to engage with.” In $CURRENT_YEAR, this is achieved through black boxes colloquially referred to as “algorithms.” The user is unable to know exactly why a given piece of content, an article, a video, etc, was presented to them. In more and more cases, the platforms are unable to explain even to themselves how these algorithms come to a conclusion given a set of parameters. This is dangerous for users, and frankly society to greater extent as well.

Every recommendation system, search engine, and the like suffer from bias, an inclination or opinion. To be clear, that’s not inherently bad. It would be impossible to suggest one thing over another without having some criteria to base that decision upon, and because the notion of “better” is ultimately a subjective one, rather than an objective one, it is a bias. These biases becomes bad when they become hidden from users of such a system, as there’s a very good chance that at least some of the biases are again, at best, indifferent to them, and at worst, actively harmful towards them. Many of the ostensible foundations of society (at least in the parts of the world I’ve lived), such as democracy and free markets, are predicated on its members being informed and rational actors. Paradoxically, we live in a time where the people of these societies have access to more information than ever before, yet the “end of history” has been in the rear-view mirror for most of my life. Clearly at least one of our assumptions is wrong. I’d argue that the idea that people today have the ability to be informed in such a way as to make decisions in their best interests rationally is wrong and that most of us are no better than our counterparts were a century ago. Ironically, the tools with the greatest chance of correcting this problem are currently being used to perpetuate it at a scale never before seen in history.

In conclusion, before sharing a link, one should ask themselves where they’re sending others. Understand that, in principle, wherever you send them, they can’t come back. Are you leading them down a dead end? Or worse, into a walled garden? Or are you sending them off to just another node on the graph, rife with opportunity to explore more links to meaningful and enriching information? I’ve always had links to this site on my other presences on the web, such as social media, and I used to have links back to those social media platforms on this site. Given the direction we’ve been heading in, I think that’s for the best.