-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
deleting a variable mid function #594
Comments
@pluto439, Also how would it be useful at all? |
Sometimes it seems like what someone is trying to accomplish doesn't make sense. And that makes it tempting to respond with curt answers like these. But they can come off as rude and make the other person feel defensive. It works out better for the community if we give each other benefit of the doubt. When I'm confused about what someone is trying to accomplish - and I must confess I am confused about what @pluto439 is trying to accomplish in this issue - I try to find out more details about their use case. Can you give a code example of what this would look like and how it would work? Does variable scoping accomplish this? e.g. fn foo() {
var a: i32 = 1234;
{
var b: i32 = a + 10;
// here we can use b
}
// here b is "deleted" AKA "out of scope"
} What issue are you running into that makes you want to delete a variable mid function? |
@andrewrk, I was wishing he would explain it would it be useful at all? I admit my wording was bad. If we assume removal from memory then it is an issue that cannot be achieved without redesign of language semantics and custom calling convention For lexical scoping it doesn't make any sense unless a variable name is going to be reused and @andrewrk mentioned variable scoping can achieve "removal" |
@pluto439 JavaScript doesn't let you delete variables once declared, with the exception of global variables which can be deleted in a limited number of cases. So are you talking about assigning them something like a |
Well, here are a few examples from my javascript code. They mostly help me figure out my own code, so that I can see where exactly the last use of this variable is. I just don't like to leave trash behind, just another safety net for me.
Example of what I want to avoid, search for }}}}} https://pastebin.com/L5L8VpsC . It's not mine btw. Probably just having one code block would've been enough here, it's probably written for compilers that don't support creating variables mid function. Also I'd like to avoid how golang handles errors returned from their functions, that they stay for the rest of the function. Have to always change
If I'll make a language, it will have an operator
Creating extra code blocks sort of works, but I dislike that I have to define variables in advance. Need to specify type of a variable, I'd like to move towards C++ |
@pluto439, deleting variables like that is against strict mode rules and possibly prevents optimizations that jit compilers can do. Meaning what you are doing is actually not only bad practice, but possibly also harmful for performance reasons. |
|
I could see some usefulness in dropping a variable early though, so as to avoid accidental use of the wrong one without the rightward drift of another scope. Don't know if it warrants a new feature but it's an interesting thought. |
@Ilariel In those links, it's more about not changing object's layout for nothing. I think you're fine if you delete local variables with |
Regardless of how JavaScript works, there's a feature proposal here that's in line with the zen of zig: communicate intent precisely. And this feature seems pretty well defined: some mechanism to explicitly remove a local variable from scope contrary to the stack-based This doesn't seem like a bad idea to me. It does seem pretty complicated for very little gain, though. An example of the complexity is that you should only be allowed to remove a variable from scope if you're at the same scope level where it was declared (can't conditionally remove a variable, etc.). Also it would be pretty silly to remove a variable from scope right as it's going out of scope anyway. I don't want anyone to think that that would be a "best practice" or anything, so we might consider making that situation a compile error. This feature is a lot like access modifiers: the main purpose is to prevent programmers from doing something. Access modifiers are extremely useful in large code bases, because they specify between a symbol being visible only in this file or being visible to the entire project. That's a huge difference, and so access modifiers can be very valuable. However, this proposal has a much more modest gain; it only specifies a symbol being visible until the end of the block, or not. Zig has a precedent for expecting a code reader to understand the entire function body in order to understand how code behaves (e.g. Let's keep this discussion about Zig, not about JavaScript. Thanks! |
@Perelandric |
@thejoshwolfe |
Wow! That's quite a novel usecase. Let's leave this issue open, and could you please open a new issue discussing that usecase. I would imagine there are many obstacles that would need to be overcome in addition to variable scopes. |
What would this code do?
|
Syntax error. delete would need to be at statement level. and if you put curly braces around the delete statement, then it's a semantic error, because deleting a variable would need to be in the same block as the variable was declared. |
I found that this currently compiles:
Perhaps this could be reinterpreted as "deleting a variable". |
Yeah, that's perfect, actually. With (planned but not yet implemented) compile-time undefined value tracking (see #597) this accomplishes the goal of removing a variable from scope, by communicating the correct intent to the compiler. In fact this is such a satisfying solution that I will close the issue now. |
from #597 (comment)
A stack variable in zig exists in the assembly instruction at the beginning of the function that increases the stack pointer by some number of bytes in order to fit all the stack variables. So if you want a variable to stop existing completely, what you are actually wanting to do, is to replace one function definition with another, while the software is running. This is hot code swapping (see #68). If you use a variable earlier in the function, and then want to delete it, there's nothing to delete. You still needed to increment the stack pointer at the beginning of the function by some number of bytes to fit the variable, for the time that you used it. |
Actually, local variables exist in registers. They are placed on stack only if there's not enough place in registers. There's no need for code swapping. It's called "liveness analysis", and it's actually done automatically by all good compilers, including llvm. My "delete variable" is more for programmer itself, so that he can know where exactly the scope of a variable ends, and so that he can use this name again without any care in the world. Technically, you could use |
Technically It's needed for making long functions easier to write. And that's needed for good repl. |
Deleting a variable is actually surprisingly simple. You just need to:
Liveness analysis from llvm is quite likely to do the rest of the job. The only problem that may appear is with debug information, debugger may be confused that there are two variables under the same name. It will make long functions easier to write |
Variable deletion will be really handy when you need to chain variables:
And when you need to create a temporary variable that will never be used again, like error codes:
|
When C first appeared, it was required to place all variables at the beginning of function. Later, it was possible to declare new variables anywhere in the function. Being able to delete variables seems like a natural progression. |
I think I can liive without it, by just putting comments |
Python uses this error message for undefined variables, or recently deleted variables. Just for the future reference. It could probably add "it was deleted just above at line xx". Error messages are a very important part of any language. UnboundLocalError: local variable 'a' referenced before assignment |
Should you be allowed to use a variable in a defer that gets deleted? {
var file = open(path);
defer file.close();
del file;
var file = open(path);
defer file.close();
del file;
} what about goto rules? {
var file = open(path);
retry:
do_something(file);
if (foo()) goto end;
del file;
if (bar()) goto retry;
end:
var file = open(path);
file.close();
} |
I'd remove defer from the language, because there is no way to fire defer mid function. It forces you to create more functions that is really necessary. (This needs checking.) del and goto is quite an example, wow. How about this. With var/del, compiler should know what variables exactly are available at every line. It should know what register stands for what variable, and what stack address stands for what variable. I guess, by gotoing after Trying to goto in between var and del will mean that variable will exist, but it's not guaranteed to contain anything meaningfull. It's responsibility of programmer to make sure that this variable will get initialized correctly. This probably should be an compile-time error, but I want to give programmer a freedom to shoot himself in the foot. Using uninitialized variables is an interesting optimization. Could turn deleting a variable without calling a destructor into a compile-time warning. I want to keep it optional, because programmer may have a full list of all variables that require destructors somewhere separately. Or he may just leave all clean-up to os. Maybe there will be a way to mark a variable as [dont-worry-about-clean-up]. |
@pluto439 what you are proposing is complicated and I'm not convinced that it is sound. You might be describing something that is a contradiction. What you are proposing is so complicated that in order to explain how it should work, I think you should go implement it, and then report back your findings. |
So, there's no way to delete a variable in llvm? I get so confused when I read their docs. |
Once the debugger will be complete, I'd like to display all "undefined" variables as "?". It will be more intuitive, I think. It's for variables that may contain garbage, and it doesn't really matter what really is in them right now. |
Using code blocks will work almost the same. Except you don't have to delete absolutely everything on block end, you may delete only some variables and keep others. |
I have been very busy lately, so I am just now trying to read through this proposal. I am not understanding what problem it really solves. If you want to reuse variable names because you have a long function, then should not that function be refactored? If it is to help the compiler, the liveness calculations in LLVM are pretty good and it will reuse registers when it determines that the variable is no longer used. If you need temporaries, then the "automatic return value as the last value in a block" aspect of Zig should do what you want, no? I see that used all over in Zig as a very nice idiom. Zig does not need C's trinary operator because of this. @pluto439, I think the debugger is probably one of the biggest problems. You would need to keep some sort of state as to which variables were defined at every line of code as far as I can tell. I can set a breakpoint anywhere. So, I cannot (without massively invasive code generation) determine which variables are visible as the program executes. That must be stored in the debugging information. The ability to use code blocks seems like it gives you everything you want, but it is clean and straightforward. It also does not require the removal of defer. Sorry that I am not understanding why this change is worth the apparent amount of effort :-( |
I'll just do it by using a preprocessor. A preprocessor that creates
It's not that necessary, I'm just trying to make programming in C more clean. People go to great lengths just to make programming more secure and comfortable, why not start on the language level.
There's no reason to use a ton of small functions instead of one big function. Big functions are usually easier to understand and write. Code Complete agreed with me on that.
It is to help the programmer. So that he doesn't have to read the whole function to see what happens with some variable.
Example?
I don't want to create code blocks for every small thing. I want to keep as much code on the same level, flat. Code blocks ain't that straightforward, del is more straightforward. Click here #594 (comment) , it's as straightforward as you can get.
If you just use preprocessor, it's not a problem at all, except working with debuggers is now more annoying. If you do it on the code generator level, it shouldn't be that much more complex than what compiler and debugger are already doing. They already have support for creating a variable mid function and code blocks, deleting a variable is just one step forward. If you intend to reuse third party debuggers instead of writing your own, then yes, it's problematic.
And I'm not sure on this one, but I think inside Even their names are bad. I don't understand why zig even has |
I used the term "liveness analysis", apparently it's also called "stack slot coloring". The thing that decides how local variables are stored. I was pretty much told to just do it myself. I'm just gathering notes here at this point. I'll probably do it. |
@pluto439, Don't take this badly, but given we seem to have found a better solution for similar behaviour, this issue is already closed, what you are suggesting doesn't seem to be worth the time to implement it unless you do it yourself and the benefits seem to be small if non-existent, I recommend you gather notes as you said you are and do extensive research on this idea of yours, C and Zig as it also seems that you haven't really understood how things work as you are comparing apples to oranges. Also check whether the implementation and usage would fit Zig Zen and the syntax when you have done it. After that open another issue (or post to this issue).
First of all, no it is not a replacement and it is quite different when compared to the Defer in Zig is closely related to C++ scope guards and Go defer https://blog.golang.org/defer-panic-and-recover while finally tends to be part of the try-catch-finally chain. As Zig doesn't have exceptions built-in to language and it uses low level error codes instead there is no reason for try-catch-finally. If you need a more detailed "exception" can still return an enum which contains a result or detailed error struct if you deem it necessary due to error case being recoverable and so common. Try-catch-finally usually forces one to wrap all operations which can return an error or rather throw an error into a try block and then you create a catch block which catches the exceptions, possibly even one for each different exception depending on the language and whether the function should deal with them separately. Then as the last thing after try and possibly catch, finally is run. Not only does this possibly hide information from the programmer as you can't visibly see which function or operation inside try block can throw an error without looking at them. This is why you have to deal with error codes when possibly returned and if you use an enum you would probably use a switch so in both cases you can visually see immediately which are possible error sources. Also If you look the defer documentation you can see this example part
Defer executes at the end of the current scope! This is clearly different behaviour from finally. The most common use case for defer is most likely memory and resource management and you wouldn't want to wrap your allocations into try-catch-finally blocks because they just don't do the same thing.
You can't compare apples to oranges like this. Catch and throw have barely anything else to do with setjmp and longjmp other than on lower level they can be used to implement the former. Actually I'm a bit confused why are you even comparing those since I don't think that they are even in the std and when you search Zig repo only place where you can even find setjmp and longjmp is in the c_headers and deps (so I guess they aren't in the std after all). Anyway considering setjmp are C standard library functions, and if implemented in Zig they probably should have similar functionality. See http://en.cppreference.com/w/c/program/setjmp for setjmp and http://en.cppreference.com/w/c/program/longjmp for longjmp functions in C standard library. As you see they are quite different beasts from throw and catch.
I also feel that I need to address this too
This is my opinion so someone else might say something different, but you can rather easily find arguments for both large and small functions, but usually it is more about modular reusable code that can be maintained easily. Smaller functions that are shared with multiple longer aren't inherently bad if they improve readability and they don't abstract away necessary information. Large functions aren't bad either unless they for example contain duplicated code that could be shared and inlined or do so many different things that probably should be split into different functions just to ease maintainability. After all self-documenting functions are best code and when you have a really large function that is called something like |
This is getting in the territory of exceptions, which I created a topic about here #578.
Thanks for checking it! I was thinking zig worked like golang. Here is an example, they recommend to create an extra function golang/go#3978 . To me this looks ugly, using advanced features to plug holes in the language.
Do you know if
Functions like "open_window" or "init_physics" can get pretty large. And something that is used at the beginning may never be used again. Need to make lifetime of variables as short as possible, it will make code easier to read. Having too many functions actually makes code harder to read, control flow is hard to figure out, it jumps all over the place, need to have tools for code navigation to find anything.
Ain't they both are used for exception handling? I think they are just different names for one thing. They work just slightly differently internally, with longjmp not being able to chain/nest exceptions properly. Read #578 for more info, around this part: "Linux uses different approach to exception handling than windows, because windows method was patented at the time. They still use setjmp/longjmp since then." I don't like how functions are merged with exception handling in golang, functions should be just functions -- a piece of code that gets called and returns. I want my program to execute linearly, and only create functions when it's really necessary. If a piece of code gets executed only once, and a bunch of functions only execute together in they same order, they shouldn't be functions. It really depends if there are better alternatives to finally or defer. Look at this #578 (comment) , dumped a bit of what can be. Creating extra function arguments when I need to pass something is quite annoying. |
#473 is surprisingly related, it also complains how you can't fire defer mid function. But instead of just removing This Actually, it already works like that, since on code block end, all defer gets fired. Why I'm complaining about golang bugs in zig. I'm still concerned about how this zig defer will work with exceptions. |
I was playing with ffmpeg filters recently, it's somewhat close to what I hope for https://ffmpeg.org/ffmpeg-filters.html The difference is, the "variables" are autodeleted on use. You have to make copies of "variables" manually, with I'm dreaming of a language where I can type code like |
When I'm programming in javascript, I do it all the time. Very useful, I can clearly see if some variable will not be used again.
Can LLVM do that?
The text was updated successfully, but these errors were encountered: