It would also be nice if there was a more standard system for API documentation. There’s https://valadoc.org/ but I don’t know of other ways to view API docs (other than reading the sources), and to figure out what GObject APIs map to what Vala APIs. And the list of available attributes should be more machine-readable instead of only existing piecemeal in the compiler source code and partially-documented in the documentation Attributes - Vala Reference Manual doesn’t even list all available attributes. It would be much nicer if they were machine-readable objects in source code the same way Java annotations are. I think ideally the documentation that exists separate from the API source code should be kept to a minimum.
Maybe some of this could be solved by a command-line tool in a similar way to how one can use the Python REPL to view documentation for various symbols? Something that allows looking up documentation for stuff installed on the local system, using pkgconf and friends. Including the non-API documentation. Vala should really lean into the ability to perform offline builds and development without the need for a web browser.
I don’t think Vala changed much over these few years , so your experience is still very relevant.
Now this is interesting, and I’m not quite sure what to make of this. Please help me untangle this?
Clearly, for you, “memory safety” means “it should not crash because of memory access issues”. And I get it, of course you don’t want your program to crash; and you’re not writing C exactly because you don’t want to deal with questions of memory management, you want the language to take care of it for you. Makes perfect sense.
But, this is not at all what the term “memory safety” is used for in discussions about Rust, or broader programming language context. Indeed, safe Rust code will happily crash if you, say, try to access an out-of-bounds array index — that is not regarded as a “memory safety” violation, crashing is still “safe”.
What they do mean by “memory safety” is that the crash is controlled, well-defined, and does not lead to (or rather result from) violations of the execution model. In other words: it’s not undefined behavior to access an invalid index in Rust, it is well-defined to crash (panic). Compared to C (and Vala), where accessing an invalid index unleashes “nasal demons” (undefined behavior), which may corrupt various things and eventually lead to a crash, or it might manifest in some other way.
So in that sense, something like Vala with Asan is mostly “memory safe”, since the invalid-at-the-Vala-level memory accesses are detected and the program crashes in a controlled manner. But, it’s clear that this would not make Vala “memory safe” for you, it would not solve your actual problem.
I take it that you did not investigate the crash you got in hashmap code, so we don’t know what went wrong there. Would this be different if the crash happened in Rust (panic) or Ruby (exception)?
It might sound like I’m trying to convince you that there is no issue here. I am not; rather I’m trying to understand your view better & map it to something tangible that we could improve.
I did attempt to figure out what was going wrong at the time, but not very hard. I don’t have much experience with debuggers in general, and I’m not sure I would know what I was looking at had i tried to run one. You mention running Vala with Asan. Is there any information on how to do that? Especially information that is accessible to beginners who are just picking up Vala?
I’m generally not fine with panics either most of the code i write tries to explicitly handle cases where things might not be present, or arrays might not be as long as I’m expecting.
That said, my main concern was that the segfault seemed to be coming from the hashmap. I wouldn’t expect a hashmap to segfault at all, and it’s possible that the reason the hashmap was segfaulting was due to a corruption that originated somewhere else and just happened to touch the hashmap’s memory. Additionally, segfaults from issues like this are the “good ending.” The program terminated because memory was accessed in a way it shouldn’t have been. There’s another story about a “bad ending” where the program didn’t crash because the accessed memory happened to be owned by the program, but contained data that was unexpected and invalid for that access.
Here’s another thing vala could do better: documentation
I’m looking for hashmap docs to try to understand both what i was doing and what is allowed to be done, and here’s some pages I’ve found: (as a new user, i can’t post more than 3 links in a post. pretend theres 2 more links and one is to a gnome gitlab pages docs page and the other is to the source code)
Honestly the best documentation for what the hashmap is and how to use it seems to be just reading the implementation, which is unfortunate. From what I can tell, none of the docs about hashmap indicate that calling get might return null if the provided key is not present. Obviously returning null is expected behavior, but documentation with examples for this usage would be very nice.
I also want to be clear that I’m not in this thread just to say something like “vala bad use rust.” I’m here because I want vala to succeed as a language people can reasonably use to build applications. I think glib bindings and the inheritance model maps very poorly to Rust, and Vala is a much better fit for that style of programming.
going in blind on the question, not reading yet what other says as it may influence me.
A touch of context: Hobbyist devs here, interested in the hands on and C#ness of Vala, and its leaning on C. What speaks to us is being able to focus on doing and not so much on syntax and boilerplates. We want to do clean, good, fast.
This wishlist is purely personal. Not pretending to know what real pros want.
1/ Anything but leaks
Id love to hear that sources of leaks have been plugged. Lambdas are easy-ish to avoid, but also the linter catching possible leak sources, warnings about possible leaks… It is often a bit confusing knowing what could be the source of a leak, and Vala being prone to leaking makes it a bit scary, and a bit like hunting for issues we dont even know how looks like
Documentation on common leaks would be rad but i maybe overlooked some
2/ C or C# offramp / documentation
more things from C#, more hints on how different or close to is C#, or C, would make us feel like learning Vala is also a “soft” way to learn C#, or at least open the doors to it easily in the future. Vala is a niche language, so we avoided learning it for a while because it sounded like a “why bother?..”.
An issue we encountered, but which i really start to appreciate, is that some documentation mixed in alot of C examples. It was very confusing and the people on discord may remember me Stella ranting, but now it also feels like a gentle nudge saying “youve been doing C all along. Just written differently”
3/ Winforms or Qt or Tk
Not feeling forced to use Gtk would be really cool. Winforms would also make Vala not feel stupid for development on windows. Qt would make Vala a good alternative for people who want to do more portable apps, or avoid python/C++ for their KDE apps.
Vala is fast, and not a hassle. If there was an easy documented way id give it a shot. And maybe it wont ever be good enough, but maybe something that kind of work could be enough to attract attention and curiosity
4/ What I Love About Vala
Before learning Vala, i learned Powershell, to do some GUI app to script some of our office job. It could be shared with colleagues, winforms and powershell are everywhere. Since we were on elementary, valac was there by default, and there was everything to do some script with GUI ootb, so we gave it a shot… And stayed for it.
Vala is our Linux Powershell. Alas it isnt everywhere, but damn is it convenient, you can script it and get something running quick. Anything having in mind the fun of building something quick is i feel a good fit for vala
Simpler lambda declarations, and trailing lambda syntax similar to Kotlin.
typealias IntOperationFunc = (Int, Int) -> Int
val add1: IntOperationFunc = { a, b -> a + b }
val add2 = { a: Int, b: Int -> a + b }
fun <T, R> List<T>.map(transform: (T) -> R): List<R> {
var res = mutableListOf<R>()
for (item in this) {
res += transform(item)
}
return res;
}
var ints = listOf(0, 1, 2);
var longs1 = ints.map { it -> it.toLong() }
var longs2 = ints.map { it.toLong() } // implicit it
Vala seems to be at the point where it needs to decide what it wants to be. It is currently the ownly language built around G-Objects based Classes. You can write Gnome appliciations in many languages, which is great, but all the languages beside Vala require a lot of boilerplate to do the G-Object construction and signaling.
Vala could be used for a lot of things beside a GTK API, that could take advantage of G-Objects, and I have done that.
The problem has been the amount of investment that has been put in Vala. It still has a lot of compiler level issues. I have programs that I need to move things around because they won’t compile do to claims that variables are missing, that are clearly there, and worked before I changed another part of the file. The translation to C uses a lot of gratuous code because things like closures that exist in GCC are emulated because they are not in some generic C.
This all could be fixed if Gnome took Vala seriously as a project, and if the general Free Software community would take seriously that C and C++ no longer cut it as modern languages.
I use Vala for two projects and what I really, really need is an up-to-date valadoc: a lot of libraries are several versions behind. Also, the 100% client-side search would be great.
Then, a simpler wish is to reduce the number of warnings in the generated C code. But of course, this is very, very secondary. I can live with it, but I would love to have much less of them.
I would like to remind folks asking for Vala to become “more generic” that it was already attempted in the past—with the “Posix” profile and namespace, compact classes, etc—and that it introduced a lot more maintenance and design issues than users.
A generic programming language that does not conform to a specific platform requires a lot more effort than just taking Vala and “de-GNOME-ifying” it; and in doing so, you now have to contend for mindshare in a fairly saturated market with very little going for you. Unlike in 2006, there are plenty of high level languages that people can use, and the selling point of Vala was always a tight coupling with the GObject/GTK ecosystem. Getting rid of that won’t increase the visibility of the project: it might just as well sink it completely.
Very right. Il will retract it from my wishlist.
And add that yes, maybe Vala could benefit from leaning heavier into its strength…
Maybe cleaner UI? There was a conversation on Matrix about Vala spitting eeehhhh code for UI, which convinced me to look into blueprint. Or Blueprint enhanced integration?
Vala treats creation of a strong reference as an operation that can be done implicitly - but this approach does not work well in a reference-counted (as opposed to garbage collected) language since it’s prone to quietly introducing reference cycles. This affects both lambdas and object methods, necessitating ugly workarounds that also produce an unsilencable warning. This is arguably actually worse than doing manual memory management since instead of having to do the right thing yourself you have to worry about compiler doing the wrong thing. Rust GLib bindings handle this by having a macro for explicitly specifying how variables should be captured.
Valac has many failure modes, some of them very annoying: printing an error (the expected, typical), emitting invalid code (so now you have to read generated C, also fairly common), crashing with an internal compiler error (quite uncommon but happens), even hanging in an infinite loop (actually happened over a missing closing brace, very rare but oh so painful to fix).
Debugging in general typically means having to deal with generated C code, although this would be nearly impossible to change without having Valac produce binaries directly.
Unsilencable warning when doing a basic RAII guard (assigning to a variable that is never used but its destructor is relied upon). On the contrary, constructing an object that gets immediately destructed does not produce a warning.
Structs are passed by a shallow copy, leading to a very easy memory corruption if the struct has pointers that get modified by the callee. The biggest issue described here is caused by this. This is unfixable without a major compatibility break, but that doesn’t make it less of a footgun.
Valac development isn’t particularly fast - in my experience fairly focused MRs taking over a year to merge is typical. This has really discouraged me from trying to fix further issues I’ve encountered as status of fixes would be uncertain.
Nullability handling is not in a great state - it’s done the “traditional” way with null being a possible value for reference types instead of having an explicit Option type or such (as syntax sugar, without changing the underlying representation) the experimental non-null mode. Compared to Rust nullability correctness in bindings isn’t taken as seriously. Experimental non-null mode is basically unusable in practice due to both binding issues and control flow analysis leaving a lot to be desired (for example, if (x != null) { frobnicate(x); } is an error if frobnicate expects a non-null argument; you must explicitly cast to non-null with (!)).
weak references are the same as unowned; this can’t really be fixed without breaking compatibility both due to the resulting ABI change and due to how upgrading a weak reference to a strong one should be an explicit operation.
Some nifty language features are completely undocumented (e.g. params, lock). I understand that some are experimental (or, perhaps, abandoned?), but I shouldn’t have to read tests for valac just to learn some syntax exists.
I really like Vala when it works, but it feels like I keep bumping into issues all the time when doing something ever so slightly off the beaten path - or, in case of circular references, something presumably near the middle of the beaten path. I also enjoy Rust’s memory safety guarantees very much, but they aren’t the main reason I went looking for an alternative to Vala in the first place and I realize it would be nearly impossible to have something similar in Vala.
P.S.: I’ve compiled this list of issues some time ago to hopefully write about it someday, but it’s very nice to have someone be proactively interested in it.
Those are codegen bugs, we should “just” fix them. Do you have specific issue reports I could look at?
I’m hoping that we could work something out for this. Perhaps negotiate something with GDB folks so they provide special support for Vala (or all languages that compile via C, Vala is not unique in this), or perhaps valac could post-process DWARF that GCC emits (or emit its own DWARF directives), or proxy and rewrite the GDB protocol, or something.
Yep, should be easy to fix: if a struct has a destructor, don’t emit the warning. And/or add the convention about starting variable names with an underscore to intentionally ignore.
But also, RAII is more of a C++ and Rust thing. Java and Vala thing is finally { } blocks.
Huh? Well, structs are value types, the value is indeed copied when you pass it. If you want to pass a value type by reference, use a ref parameter.
What’s the memory corruption? Is this about raw pointers? If you’re using raw pointers, you should be extra careful.
Yes; the default (lax) nullability mode is… not serious about nullability. The strict mode is serious about nullability, but it’s incomplete. We should get it over the finish line, including making your frobnicate example just work.
I don’t think there’s an ABI change, at least as long as we’re talking about private fields / variables. What case are you thinking of?
You don’t necessarily have to upgrade a weak ref to a strong one, you would be able to just use the weak one, it should be usable just as a nullable unowned reference. If you do want to upgrade it, you would write:
Could you explain why that would be useful? How would you use it, what would you extend?
I’m familiar with extension methods in other languages, like Kotlin and C#, and with how extension methods are useful in those other languages — but how would it be useful in Vala?
Hm, I guess, for instance, libdex variants of various GIO API (example) could be represented as extension methods.
I agree with capture list for lambdas, but note how in my linked code I’m creating delegate from a method, not a lambda. Maybe one should be asked to explicitly cast the method to owned or unowned (otherwise, a warning should be generated).
Shallow copy is the worst of both worlds: it’s neither as safe as a deep copy (where the callee would get an own copy of the string and could do whatever with it) nor as fast as passing by reference (where there would be only one struct all along, so the caller would see the updated string) and the only scenario where you get suddenly hit with UB out of blue. A deep copy isn’t something all structs have an API for (once again, see e.g. DataList). I really believe structs should be passed without copying by default, even if that would be a breaking change, as it removes unexpected UB like this. Also, consider that structs are already heap-allocated and passed into functions by reference (to a copied struct) - Foo bar in Vala becomes Foo* bar with a copy at call site; copying behavior should be retained for [SimpleType] structs which are passed in the traditional C way (Foo bar in Vala becomes Foo* bar) and probably should be used more widely.
FWIW, I think the current behavior (copy at call site) is also surprising when calling Vala from C, as there won’t be an automatic copy at call site in C, and any modifications to the struct in Vala will affect the original. Maybe this specific situation could be improved by doing the copy in the callee, but this does not solve inherent issues of the shallow copy.
Perhaps not for private variables, but I don’t think there’s anything preventing you from making a public weak variable. Such code would not be compatible.
You can’t just use a weak reference directly as that would introduce a race condition (see API of WeakRef). Making upgrading it to a strong reference implicit is also a bad idea, I believe, as it would compile to a g_weak_ref_get/g_object_unref duo every time the value is used and make writing a TOCTOU race a little too easy:
weak Foo? bar;
...
// Another thread holds a strong reference to bar
if (bar != null) { // g_weak_ref_get, comparison successful, g_object_unref
// The other thread drops the strong reference, bar becomes null
frobnicate(bar); // g_weak_ref_get returns null despite the previous check!
// g_object_unref
}
With explicit upgrading:
weak Foo? bar;
...
// Another thread holds a strong reference to bar
var? bar_strong = bar.get(); // g_weak_ref_get
if (bar_strong != null) { // comparison successful
// The other thread drops the strong reference, but we got our own
frobnicate(bar_strong);
}
...
// g_object_unref at the end of the scope
Here is the list of features that make me use Rust for GObject. I consider these features non-negotiable ; or at least, for me to use a language that does not have one of those features, it would need to have an advantage over Rust that balances things out. Some of these features might already be there in Vala, but I barely know Vala.
memory safety. (I don’t want to investigate memory related issues ever again. I don’t want to have to manually increment or decrement reference counts.)
thread safety. (Rust and GObject are not the best friends there either. There would be many things to improve. But I think it’s probably a complex topic to adress.)
first-class async support.
type safety. I am thinking, fat enums (Option, Result, but also custom fat enums) and pattern matching. Also, I never want to look at a function signature and not know whether an argument or return value might be NULL.
a standard library as good as the Rust one.
an ecosystem of libraries as good as the Rust one.
Then there are less technical issues.
confidence in the long term support of the language.
good documentation. (I don’t judge Vala here ; I don’t know the status of its documentation.)
So for the reference, the code you’re currently writing looks like this:
unsafe extern "C" fn try_create_calendar_async_default(
manager: *mut ClepsydreManager,
_collection: *mut ClepsydreCollection,
_name: *const c_char,
_color: *const GdkRGBA,
cancellable: *mut GCancellable,
callback: GAsyncReadyCallback,
user_data: gpointer,
) {
let task = unsafe { g_task_new(manager as *mut _, cancellable, callback, user_data) };
unsafe {
g_task_set_source_tag(task, Self::try_create_calendar_async_default as *mut _)
};
let error = glib::Error::new(
ClepsydreError::NotImplemented,
"The method try_create_calendar_async is not implemented",
);
let error_ptr = error.to_glib_full();
unsafe {
g_task_return_error(task, error_ptr);
g_object_unref(task as *mut GObject);
}
}
unsafe extern "C" fn try_create_calendar_finish_default(
_manager: *mut ClepsydreManager,
async_result: *mut GAsyncResult,
error: *mut *mut GError,
) -> *mut ClepsydreCalendar {
unsafe { g_task_propagate_pointer(async_result as *mut _, error) as *mut _ }
}
unsafe extern "C" fn try_create_calendar_async_subclass<T: ManagerImpl + ObjectSubclass>(
ptr: *mut ClepsydreManager,
collection_ptr: *mut ClepsydreCollection,
name: *const c_char,
color_ptr: *const GdkRGBA,
cancellable: *mut GCancellable,
callback: GAsyncReadyCallback,
user_data: gpointer,
) {
unsafe {
let task = g_task_new(ptr as *mut _, cancellable, callback, user_data);
let manager = Manager::from_glib_borrow(ptr);
let collection = Collection::from_glib_borrow(collection_ptr);
let name = glib::GStr::from_ptr(name).as_str();
let color = RGBA::from_glib_borrow(color_ptr);
let imp = manager.unsafe_cast_ref::<T::Type>().imp();
let future = imp.try_create_calendar_future(&collection, name, *color);
glib::MainContext::ref_thread_default().spawn_local(async move {
match future.await {
Ok(calendar) => {
let calendar: *const ClepsydreCalendar = calendar.to_glib_full();
g_task_return_pointer(
task,
calendar as gpointer,
Some(std::mem::transmute::<
unsafe extern "C" fn(*mut GObject),
unsafe extern "C" fn(gpointer),
>(g_object_unref)),
);
}
Err(error) => g_task_return_error(task, error.to_glib_full()),
}
g_object_unref(task as *mut _);
});
}
}
klass.try_create_calendar_async = try_create_calendar_async_subclass::<T>;
unsafe extern "C" fn try_create_calendar_finish_subclass(
_obj: *mut ClepsydreManager,
async_result: *mut GAsyncResult,
error_ptr: *mut *mut GError,
) -> *mut ClepsydreCalendar {
unsafe { g_task_propagate_pointer(async_result as *mut _, error_ptr) as *mut _ }
}
klass.try_create_calendar_finish = try_create_calendar_finish_subclass;
which is ripe with “unsafe”. The equivalent code in Vala looks like this:
public virtual async Calendar try_create_calendar_async (
Collection collection,
string name,
Gdk.RGBA color,
Cancellable? cancellable,
) throws Clepsydre.Error {
throw Clepsydre.Error.NOT_IMPLEMENTED ("The method try_create_calendar_async is not implemented");
}
Which one, in your eyes, is safe, and which one is not? Why is the former preferable to the latter? And in this context, what does it mean when you say that safety is non-negotiable?
Instead of Rust’s Option and Result, Vala has nullable types (e.g. Cancellable?) and errors that you can throw and catch (these compile to GError).
There’s no real pattern matching though, nor tagged unions. But how is that related to memory safety?
Vala’s standard library is GLib. Is that as good as the Rust one? How do you judge?