Will the 'using' keyword be deprecated one day?

I think ‘using’ is useful for passing data to callbacks while making it feel like a regular procedure, but I’m worried about forward compatibility.

Even if it doesn’t get removed, codebases might start to ban the use of it because it is bad practice. I am also maybe focusing too much on the ergonomics and should just get used to the more explicit version. Here is the code example, I’d like to know what people think:

package main

import "core:fmt"

Task_Data :: struct {
	a, b, c: int,
	p, q, r: f32,
}

get :: proc($T: typeid) -> ^T {
	return transmute(^T)context.user_ptr
}

task_implicit :: proc() {
	using data := get(Task_Data)
	c = a + b
	r = p * q + f32(c)
}

task_explicit :: proc(ptr: rawptr) {
	d := transmute(^Task_Data)ptr
	d.c = d.a + d.b
	d.r = d.p * d.q + f32(d.c)
}

main :: proc() {
	data := Task_Data {
		a = 1,
		b = 2,
		p = .5,
		q = .2,
	}

	context.user_ptr = &data
	task_implicit()
	fmt.println(data)

	task_explicit(&data)
	fmt.println(data)

    // Task_Data{a = 1, b = 2, c = 3, p = 0.5, q = 0.2, r = 3.0999999}
}

TLDR: You can use it if you want and not worry about it getting removed, but beware of the consequences, namely readability.


As for the idea that you could use this keyword in general, I’d say it’s a bad idea.

This isn’t what I’d consider to be ergonomics as much as “hiding for the sake of convenience”. To make my point clearer – using inside a scope makes it hard to know where a value is coming from. Could be a global variable, could be a local variable.

x := 3

get :: proc(state: ^T) -> int {
   using task := get_task(state)
   y := 100
   // 100 lines of code
   return x
}

If you take the example above, it would be pretty hard to pinpoint that the variable x is obtained from the task structure, as opposed to, for example, being declared somewhere in the 100 lines before it is returned, or the global variable above.

Lets assume that we’ll just keep our functions short.

Okay, but then there is the other problem: understanding the impact a statement makes. Take, for example, x=3. We all know what assignment means. Now take

using task := get_task(id)
sync.guard(task.mutex)
status = .Ok

Any idea why task.mutex is being locked, when it’s not apparent that it is being accessed? Do we know, for sure, that status = .Ok doesnt mutate any values outside of the scope of the function? The answer is that we don’t know, without fancy text editor features like Go To Definition.

When multithreading is involved, you need to be able to understand the state you’re mutating. Is the state global? Is it shared? To be able to communicate these things effectively through code the first step is to avoid hiding where its coming from. using keeps us away from understanding shared state and the effect of mutating values.

The other point I’d like to make is that the usage of using is pretty hard to make consistent. If you have two structs with overlapping fields, you can’t be using them both. One of them has to go. Which one? Maybe you decide that in this case you don’t using. But then you have another function where you do use it. You end up in a place where code that does similar things does not look similar across functions.


To answer the question directly, no, using keyword is not going to be deprecated. Because it has a valid usecase in structs subtyping, which is fairly convenient in certain cases. The using in scopes might get a flag, just like the do keyword got one, that bans the usage of the keyword, but still might be optional.

As for your specific usecase, I’d say maybe. I don’t have a strong opinion here. Personally, I’d keep my code dumb and simple and not try to go for cheap hiding tactics to make it appear lighter than it actually is (you are, after all accessing a state beyond the scope of the function).

2 Likes

Thanks for the detailed response! I do agree that it can hurt readability in most cases which is why I generally don’t use it. In this case however, It’s essentially just a workaround for not having capturing closures in the language.

I’ll also make it a point to always write the struct right above the procedure to make it clear which fields are being inherited, not much different from scrolling up and seeing the inputs to a procedure.

But yeah, it’s a slippery slope, and has a bad reputation for reasons you have outlined above, which is why I was worried it might disappear. Good to know that won’t be happening anytime soon.