v icon indicating copy to clipboard operation
v copied to clipboard

Module Variables

Open StunxFS opened this issue 1 year ago • 20 comments

Describe the feature

Weeks ago, on V's Telegram channel, I had proposed the idea of ​​introducing module variables.

The objective of this idea is to remove __global from the compiler completely, since module variables could replace its function (in a safer and more idiomatic way).

Global variables in V require a flag to work (-enable-globals) and can be accessed from anywhere in the code, plus they are always mutable, making them somewhat unsafe to use.

Additionally, with the implementation of module variables, you could make const stop receiving values ​​that are computed in runtime, because static would fulfill that function perfectly. Then const would receive pure values ​​that can be computed in comptime.

My suggestion is to use the static keyword (already used to declare static variables inside functions) to declare module variables, which can be declared as pub and mut as needed:

// report/report.v
module report

pub static mut errors = int(0)
// main.v
module main

import report

fn main() {
    report.errors += 1
}

Use Case

In addition to what has already been said, the idea is also to make a distinction for constants, which currently receive all types of values ​​(both runtime and comptime), which could confuse new users who are learning V, but come from other languages ​​where const receives pure values ​​that are computed in comptime.

Proposed Solution

No response

Other Information

No response

Acknowledgements

  • [X] I may be able to implement this feature request
  • [X] This feature might incur a breaking change

Version used

latest

Environment details (OS name and version, etc.)

anywhere

[!NOTE] You can use the 👍 reaction to increase the issue's priority for developers.

Please note that only the 👍 reaction to the issue itself counts as a vote. Other reactions and those to comments will not be taken into account.

StunxFS avatar Oct 31 '24 14:10 StunxFS

Like a singleton pattern? Would be able to be shared among threads?

jorgeluismireles avatar Oct 31 '24 14:10 jorgeluismireles

I like the idea of consts being comptime only.

I don't like having mutable globals in the language, not hidden behind ugly keyword and a compiler flag.

medvednikov avatar Oct 31 '24 16:10 medvednikov

As a user and upon reading this, have various thoughts about it.

1) Thought the point of keeping global variables was low level and compatibility issues with other languages.

If that is the case, then wouldn't various V users want them to be "accessed from anywhere in the code"? For the turned on global variables to act and be similar to the situation that people are use to in other low level languages... Not quite sure how module variables would be received, when V's __global is as easily identifiable as const, even for newbies.

2) My understanding is that team V wants to discourage both global and static variable usage.

Some time ago (upon me starting to play with V), I posted this discussion, #13926. The sentiment was for functions to use structs at the module level. Static variables only within functions and inside unsafe.

However, it can be argued that the documentation fails to make this usage clear, with examples of structs replacing usage of global and static variables. This might be leading to confusion or even frustration, because it's a different (even if arguably better) way. I do want to make it clear that I've become accustomed to V's way, and agree with the reasoning, but I do think team V may want to be mindful of the difference for people coming in from other languages.

Wajinn avatar Oct 31 '24 23:10 Wajinn

Module variables could be used to handle C global variables perfectly, and this could also fix issue #22691, making it so that a module variable can be identified as external and thus used correctly in the code.

module main

@[c_extern('gVar')]
static mut c_var int

fn main() {
    c_var += 1
    println(c_var)
}

Of course, module variables could also be shared safely between threads, as long as the variable is not mutable.

StunxFS avatar Nov 01 '24 15:11 StunxFS

Is this a feature now or not yet?

Globals have their use, but this language not having a module level private mutables, is decisive and could make me rework my project to another language.

There's no way I have to write a getter/setter and an intrincated named global variable just to expose some variable, not only harder than C, but slower (20 more assembly instructions minimum per variable access, heavy).

I like this approach using static keyword because it's C-like idiomatic.

Hope that this comes to a commit soon so I can advance in my game engine.

Module level variables are not unsafe or unparallelizable, the junior can learn to use it safely with just a two paragraph page in docs.vlang.io, and for parallelization just atomic fetchs shall do trick.

I think that this design of getting rid of module level variables is unasintomathically killing my beloved V, and it's leading whereareas no one likes.

Maybe it's just me that I don't know how to use it, if so, please update docs because I spent 4 hours reading them I did't found anything

Warmest regards, Bruno.

CC: @medvednikov

PS: Please don't make me go back to Vala, I dislike GLib.

bruneo32 avatar May 26 '25 10:05 bruneo32

@bruneo32 V has a hidden static keyword you can use. Works just like in C. We implemented it for C2V'ed code.

medvednikov avatar May 26 '25 13:05 medvednikov

also why not just use __global?

you prefer static because it's more clean?

medvednikov avatar May 26 '25 13:05 medvednikov

Also I guess we can make __global module scoped. Will break some code, but it seems to be a cleaner solution. @spytheman what do you think?

medvednikov avatar May 26 '25 13:05 medvednikov

You know me... I think __global should be harder to use, not easier.

Those who can't wrap their heads around not using globals, or are unwilling to put up with the extra steps to do so, should probably look at a different language.

And yes, static is simpler to use, you don't need any extra command line arguments to enable it, etc. - but at least it's namespaced, not total program-level globals.

JalonSolov avatar May 26 '25 14:05 JalonSolov

Hi everyone, I mean to use some kind of module level variable that can be mutable I don't need to access it from another module; for example player.v can have player data variables and just expose functions like draw, update, create, etc. I want to set variables on create and modify them in the update, but if I declare them as unsafe static I cannot access it from the other functions.

Using globals in this scenario is anoying because I have to declare the variables prefixed for each file like player_x instead of just x.

I like globals a lot for some unsafe cases like the unethical but useful global I have __global null = unsafe { nil }, I wouldn't get rid of it.

The static approach mentioned at the description of this issue would help me a lot.

But if there's a secret key to use the static keyword as you say I would like to try it. Is there any example?

Thanks!

bruneo32 avatar May 26 '25 14:05 bruneo32

fn foo() {
	unsafe {
		static x := 0
		x++
		println(x)
	}
}

fn main() {
	foo()
	foo()
	foo()
}
v run a.v
1
2
3

medvednikov avatar May 26 '25 14:05 medvednikov

I know that kind of static inside a function like C, but I mean at module level.

bruneo32 avatar May 26 '25 15:05 bruneo32

@medvednikov our proposition here is to have an experimental flag like -enable-module-statics, so to declare variables like Go's module var using the static keyword for this purpose.

Also for the previous conversation, globals should not disappear because they serve a purpose, the risk is taken with the ugly keyword + compiler flag.

Note: This is just a simple example and can be solve in other ways but the necessity for this kind of module variable would be clearer with more examples. Don't stick to it and think more ones

Example:

// game/game.v
pub struct Room {
pub mut:
    create  ?fn ()
    update  ?fn (delta f32)
    draw    ?fn ()
}

// This is hell, but globals should not disappear for it
__global null = unsafe { nil }

// This are effectively scoped globals because they are public
pub static mut room_current := &Room(null)
pub static mut room_goto := &Room(null)
// rooms/room0.v
import game

pub const room0 = &game.Room{create, update, draw}

// Module scoped variable (not public)
// Uninitialized maybe possible (?)
static mut x := int(0)

fn create() {
    // Reset each room restart
    x = 32
}

@[unsafe]
fn update() {
    // Pseudo code, it could be gg or sdl input event
    if keydown(keycode.space) {
        x++
    }
    if x >= 64 {
        // End game
        game.room_current = null
    }
}

fn draw() {
    // Pseudo code again
    draw_rect(x-8,y-8,x+8,y+8)
}
// main.v
module main

import game
import rooms

fn main() {
    
    game.room_current = rooms.room0

    mut last_room := null

    for !isnil(game.room_current) {
        // Use intermediate variable so the user
        // cannot destroy the room until the end of the loop
        room := game.room_current

        // Only if the room has change
        if last_room != room && room.create != none {
            room.create()
        }

        // Always, delta just simulated for this example
        if room.update != none {
            room.update(0.016)
        }

        if room.draw != none {
            room.draw()
        }

        // Update last room
        last_room = game.room_current

        // Goto another room
        if !isnil(game.room_goto) {
            game.room_current = game.room_goto
            game.room_goto = null
        }
    }

}

Using scoped globals could reduce the use of global variables and sanity of the general V coding. Currently instead of game.room_current I use a global game_room_current, but for repetitive files like rooms, player, entities, etc; I dislike the use of globals because I would have to name each variable prefixed with the file name, like in C.

Making polymorphism for this Room structure is actually a workaround, but it would introduce an overhead that I don't want, and that's not the point of this language.

If there's a design style encouraged to overcome this issue, it should be clear on the docs, because I didn't read anything regarding it.

Have a nice day, and sorry for the inconvenience.

Note: static mut could be substituted for another keyword like var/__var/..., since a static without a mut is just a module constant const.

CC: @StunxFS

bruneo32 avatar May 27 '25 10:05 bruneo32

@bruneo32

Note: static mut could be substituted for another keyword like var/__var/..., since a static without a mut is just a module constant const.

No, what I proposed was not only to add support for variables at the module level, but also to stop const from supporting values ​​that are computed at runtime. Therefore, you can't replace static mut with var and leave const as it currently is.

A static non-mut is very different from a const. consts SHOULD only handle comptime values. There is also no need to implement a flag to use static.

StunxFS avatar May 28 '25 04:05 StunxFS

I though they were comptime

bruneo32 avatar May 28 '25 04:05 bruneo32

No, consts are runtime, since they can be set by calling a function. That can't happen until runtime.

JalonSolov avatar May 28 '25 12:05 JalonSolov

Can it detect when constants are not dependant of a runtime variable like const num = int(5) should be #define num 5? It would be a nice feature I think

bruneo32 avatar May 30 '25 10:05 bruneo32

@bruneo32 Yes, CGen generates #define when the value is compile-time.

StunxFS avatar May 30 '25 12:05 StunxFS

Cool, but returning to the main question, are we having module mutables in a near future?

bruneo32 avatar May 30 '25 12:05 bruneo32

Undecided, so far.

JalonSolov avatar May 30 '25 12:05 JalonSolov