What features of Odin do you dislike?

I agree that “__” is much uglier than a single “_”.

But I think a single “_” is not equal to “.” because here:

entity.do_some_awesome_amazing_magical_stuff()

you see that “do_some_awesome_amazing_magical_stuff” is related to the noun/struct “entity” while here:

entity_do_some_awesome_amazing_magical_stuff()

It is much harder to see that. So, I use an uglier approach with two underscores “__”

entity__do_some_awesome_amazing_magical_stuff()

just to make the code a bit more readable sacrificing beauty :slight_smile: because I cannot use a “.” dot in proc names.

Some might say do not write in an OOP way, but I think C programmers were writing in an OOP way even before C++ and I like to write in a mixed OOP and procedural ways. I was using two underscores “__” in C too, but always wished I could use a dot “.”.

This is a very minor thing, love the language.

Thank you!

Thanks for your response.

I understand the concern about virality of a const qualifier, but ultimately I don’t think that it is inevitable that it become viral if the language only allowed it in certain places and did not present it as a variable qualifier. I attempted to provide an example of a way to offer the functionality, as a procedure parameter tag for slices and similar containers, which I do not think would become viral because it is specifically and intentionally limited to a particular place, just like #any_int is (unless I am mistaken).

I understand that you do not owe me any more of your time or insight, so I understand if you’re done with this conversation. If you do feel up to it, perhaps you can share why it is that even an application as limited as this would still be viral or otherwise a worse headache than its worth. Thanks again.

  1. How tightly integrated the compiler is with multi-threading, if i wanted for example, to integrate the compiler into something embedded, this would be an issue, at least have something like a compile-time flag for it
  2. The package name at the top, why not use the directory name instead where the file resides in? and for the main you can just use the directory passed in the cmdline
  3. When declaring variables from a proc that returns multiple, you cannot both assign and create, even if the type is the same. for example:
d0, ok := do_somthing();
// handle ok
d1, ok := do_somthing();
// handle ok
  1. Not being able to do smth like this:
data, ok := &mymap[key]```

A couple of details / not a big deal

  • being force to use a temporary variable to access an element of a map of a map
  • force to use named return variables in tuple with or_return (you said it was impossible by design, I just don’t understand why sorry)
  • can escape a reference to a scoped variable w/o compiler warnings (even with sanitizer activated)
  • no default value for members of a struct (need to use a DEFAULT const struct instead)
  • missing the my_variable.a_proc(args...) syntax that sometimes present a better semantic power than MyPackage.a_proc(my_variable, args...), or worse, MyPackage.a_proc_on_a_type(my_variable, args...) PS: I hate OOP (class, code inheritance, setter/getter, design vomit pattern etc…) and love what has been done on polymorphism in ODIN
  • indicating the package in each file (except main package, package name is folder name)
  • boiler plate around the required casts for int when enlarging (int8 to int16); that one is quite annoying
  • cannot reuse an existing declared variable x in the for x in range/array/map, as x is always re-declared / shadowed → switch to for decl;cond;post syntax.

Debug is a real issue :frowning: Still cannot debug when using switch control flow on windows

Missing “concepts” and I understand ODIN was not designed in these directions, you clarified already these points, just sharing some perspectives

  • concurrency is a library/user space thing and not a language concept. It would have been nice to have the concept available in the language SOMEHOW, to highlight potential data-race issues or the usage of inappropriate data structure (that are not concurrency safe, etc…)
  • not having a not-null pointer type: Maybe(my_pointer) is available to ensure runtime check, but some compile time guarantee would have been nice (that would lead to a new category of problems, such as initialization for non-null pointer struct/variables, etc…)
  • a basic simplistic version of a trait system (NOT the Rust way as totally bloated FMO). Would help for example to implement concurrency concepts (Send, Sync)
  • mutability in the type system (outside of compile-time const, @(rodata) and proc parameters that are always immutable :+1:), at least an immutable reference concept to indicate the pointee will/cannot be modified

Would I be granted a wish, concurrency would be the one.

To answer you points:

  • If the key doesn’t exist in the map, what are you then assigning too? I know what people want from this but it is literally pure magic what they want, and this is actually an error is the vast majority of languages too (usually at runtime).
  • I’ll explain my demonstration: proc() -> (int, bool) what is the value of int if you early returned with or_return? You could say it is 0 but the problem is that is not clear from use. This is why a named return value is necessary in this case. It’s to make it well defined and obvious what it is. In the cases of variables, it is consistent and clear that everything defaults to zero by default. When there is not variable, it is not clear if that is the case or not.
  • “Escaped reference” I guess you mean return a pointer to a stack variable. If so, then that is literally an unsafe thing to do, and no wonder the compiler warns/errs at you. It’s telling you to NOT do this. Odin is a language with manual memory management.
  • The default values are all “zero”. The reason Odin doesn’t allow for custom default field values is that would require an implicit constructor which is something I did not want. I don’t think people realize the hidden cost of this compared to the equivalent of memset(p, 0, n)
  • Odin is not having methods ever. I am not sorry about this.
  • That is necessary for having a consistent ABI for deterministic and consistent link names, without requiring an absolute import path. It’s weird, I know, but it is necessary solution to a specific problem that most people don’t think about, especially if you want an actual C alternative.
  • Shadowing is not a bug but a feature. Explicit for-loops exist for a reason, and it allows for higher control.

Regarding debugging and switch statements, please give use a minimum reproduction and make a GitHub issue for it. And we’ll see what we can do to improve this. We cannot fix things if people don’t tell use about it and how to reproduce the issues.


As for the second aspects:

  • Concurrency being a user-space thing is on purpose. There is virtually no benefit with just having it at the language level except for some nicer syntactic sugar in places. In order to check for even potential data-race issues require a radically different memory model combined with a hell of a lot stricter type system. This would then not be a C-alternative any more, but closer to something like Rust. Even Go which has goroutines and channels built-in doesn’t have compile-time checking for race conditions.
  • I’ve discussed this before but nil-pointers are literally the easiest category of invalid pointers to catch empirically. This is because modern operating systems reserve the first few pages in their virtual memory space and make them invalid to access entirely. I understand people want to prove things at compile time, but it’s a trade-off in design. Adding such a requirement for such things might sound like a good idea but requires a radically different approach to the design of declarations, which would again not make it a C-alternative any more and something else entirely.
  • Odin cannot have “traits” or anything of that nature because Odin doesn’t have methods, operator overloading, or any kind of typeclass system to allow for such a thing. The idea of a trait system requires a lot more first before you can add it. Also traits wouldn’t help to implicit concurrency concepts whatsoever—those constructs are entirely orthogonal.
  • Odin is designed to be mutable-by-default, and this is not a mistake. In C, I found that const never actually helped outside of a few areas; those areas I codified directly into the type system itself instead. const in C is trivially bypassed and such it restricts what is even possible in terms of “optimizations” because of this ability to bypass it. Odin has string which is read-only, immutable procedure parameters, multiple return values, and actual compile time constants (closer to #define that static const of which that is better described with @(rodata)).

This might sound silly/rude but literally every one of these complaints is “I want a different language entirely” and the “not a big deal” by is literally a big deal if I was to implement a single one of these. None of those are in the scope of the design of Odin and doing anything like that would make Odin not Odin any more.

6 Likes

Thanks a lot for taking the time to answer and for the explanation

For the sake of clarity, the Not Big Deal was not about changing the language, it was about using the language (not big deals means I can go around in my day to day usage of Odin).
Please keep Odin as such, I was only sharing some perspectives as per my comment.
But if there is any good user space code base/existing usage in Odin of concurrency I am honestly interested (knowing threadpool, atomics, channels etc… are all there in core)

Peace

PS: for the map of map, assignment is out of question indeed I was only thinking of accessing element by extending the existing rule of a[“b”] give you 0 if “b” does not exist to a[“b”][“c”] gives you 0 if “b” or “c” does not exist (for map[string]map[string]int for ex.). Minor detail, I just use a lot of map of map in my current usage of Odin

PS bis: the comment on escaping reference / dangling pointer is the opposite, there was no warnings given by the compiler even with sanitizer on.

I have a feature that I liked before, but now starting to dislike. It is - having the same syntax for accessing a field member even when dereferencing a pointer.

MyStruct :: struct {
   a : int,
}

x : MyStruct
y : ^MyStruct
x.a = 0
y.a = 0

This I thought was pretty nifty and saved a lot of typing but once the project got bigger, it wasn’t always clear to me if I am accessing a field member directly or deref-ing a pointer or if I have to check for nil beforehand.

curiously, that is the feature I dislike the most from C/C++.
To make me clear that im using a pointer or a copy in stack mem, I use the p prefix on procerdure parameters.

increase_by_one :: proc(pMyDescriptiveWord:^MyStruct)
{
  pMyDescriptiveWord.a +=1
}

So I know instantly what I’am accessing on the line of usage.

Nothing major at the moment, just would like more/better documentation of core. For example, encoding/json would be a complete mystery if I wasn’t familiar with marshal/unmarshal from prior Go experience. An example and proc descriptions much needed there I’d say.

Having said that, I’m guessing it’s a time and resource issue, since core:fmt is comprehensively documented for each proc and has examples, so guessing just more contributions are needed in this area.

1 Like

@flysand7 has been hard at work on documenting various parts of core, but anyone is welcome to contribute. If you find a part of the standard library that you’re really interested in, you might be the best person for the job.

4 Likes

Might be a little while before I have the confidence to submit a PR but that’s really good to know, thanks!

It’s a compromise between refactoring, clarity, and terseness. Because Odin uses ^ for its pointer symbol for pointer types and pointer dereferencing, if dereferencing was required (e.g. x^.y) all the time, the symbol ^ would have to be typed a lot more than it currently does (a hell of a lot more). And for many keyboards that is not a nice thing to do; I understand people complain about ^ already but because type declarations a hell of a lot less common than dereferencing by a few orders of magnitude.

Also, when programming in C/C++, the amount of times I want to do x.y instead of x->y is not just because of refactoring but because I know it should work. Even the compiler knows what I want with the error suggesting x->y, so why not just allow me to do it?

As for the last aspect of dereferencing a nil pointer, you do catch pretty quickly now.

4 Likes

The package name at the top, why not use the directory name instead where the file resides in? and for the main you can just use the directory passed in the cmdline

The code is not always resides in the directory with the name that is a valid identifier. Sometimes it’s something like “lib/v0.4”. And I don’t think that forcing users to not use versions in their directory names is a good idea.

3 Likes

I wish there was a way to do default values for structs. I am a big fan of zero is initialization, but sometimes I just want a default value maaaaaaan

Varargs represent “zero or more” arguments, but every time I use it I really want it to be valid for users to enter “one or more” arguments. You can do this with something like this, but it’s clunky, slower, and error prone.
f :: proc(x0 : int, x: …int) {
// combine x0 and x into another array?
// handle them separately each time?
}

I’m sure we don’t want a ton of compile-time checks, but a directive would be useful.
f :: proc(#required x: …int) {
}

Also I agree with Krzysztophoros on not implicitly broadcasting a value into an array when passed as an argument. It’s unintuitive and basically everyone is going to get tricked by it. It’s like switch statement fallthrough in C but not nearly as fun.

I also pretty much always end up deleting defer after I use it, unless for an extremely simple deallocation where I don’t care where it happens in relation to anything else.

“Using” seemed pretty neat too, but I also ended up refactoring that out of everything.

I’m the opposite - I like that there are no default values. I can count on every struct being zero initialized or use designated initializers. If I really have a struct that needs to have a non-zero default value I just write a init_my_struct :: proc() ->my_struct that returns the struct initialized with whatever default values.

1 Like

I understand the issue of variadic parameters being 0+ rather than 1+, but the problem is that if you wanted to enforce this, you’d also have to disallow the ..slice syntax. This is because then you’d have no compile time way to enforce whether that slice has at least 1 element. So the entire “0+” approach is to have very consistent and coherent rules that people will intuitively understand.

As for the broadcasting, this can be disabled with #no_broadcast however it’s on by default because that is how array-programming works in general and the procedure parameters are not doing anything special but using the pre-existing rules which exist everywhere.

I do find it surprising that you end up deleting defer after using it with regards to allocations. That doesn’t sound right to me, unless you are now using a lot of arena-based memory allocation strategies.

1 Like

I generally suggest using a single slice argument instead of varargs.
Varargs is just a slice at runtime anyway.

1 Like

IMHO the C foreign system is tedious.

In Zig / Swift (and probably others), you can import the C header and call C functions directly. For whatever reason, this is not possible in Odin and you have to manually declare everything which is time consuming, error prone and requires work to keep your binding up-to-date with the C libs.

And yes I found the rational, but still think a “default” binding would be better than nothing.

May be a good tradeoff would be a tool which generates the bindings as an odin file that you can then rework step by step “Odinizing” the code.

And yes I found the rational, but still think a “default” binding would be better than nothing.

If you understand the argument, I am not arguing binding generators but I am against the automagical imports. Having a “default” is actually WORSE than nothing because as soon as something exists, people will, use, abuse, and rely on it.

We will have a bindings generator, and I have an idea as to how to do it very well too, but it’s not the highest priority yet.

6 Likes