Now, this may seem like overthinking this, but remember that we are no longer coding in assembly: we are coding in higher level languages that let us express the same properties as assembly, but in a more human-friendly fashion. Hence the comparison made above.
I'd have expected the named pattern to be a zero-cost abstraction (the whole point of these system programming languages), i.e., that all these function to compile down to the same operation (zero-extending the enum to a size_t, and then perform a raw indexing operation, even in the named cases), but it looks like the moment we are using Attribute * attr instead of size_t idx, the current compilers get confused for some reason, and do not generate optimal code. That's more of a bug within the compiler than an issue with the named patterns, though.
This is unsafe within multi-threaded code, so Rust won't let use write that without unsafe, or without explicitely opting out of multi-threaded code, by making the "global" be thread local.
To be honest, the same problem arises in C, so I'll assume the global is actually a thread local one.
C
#ifndef __STDC_NO_THREADS__
# include <threads.h>
#endif
#ifndef THREAD_LOCAL
# if __STDC_VERSION__ >= 201112 && !defined __STDC_NO_THREADS__
# define THREAD_LOCAL _Thread_local
// Portable thread_local annotation is a pain before C11
# elif defined _WIN32 && ( \
defined _MSC_VER || \
defined __ICL || \
defined __DMC__ || \
defined __BORLANDC__ )
# define THREAD_LOCAL __declspec(thread)
/* note that ICC (linux) and Clang are covered by __GNUC__ */
# elif defined __GNUC__ || \
defined __SUNPRO_C || \
defined __xlC__
# define THREAD_LOCAL __thread
# else
# error "Cannot define thread_local"
# endif
#endif
// Above was taken from: https://stackoverflow.com/a/18298965
THREAD_LOCAL
Attributes g_attributes = { .named = {
.might = 10,
.dexterity = 10,
.intelligence = 10,
}};
void increase_global_attribute (AttributeName attr_name)
{
Attributes * at_g_attributes = &g_attributes;
increase_attribute(at_g_attributes, attr_name);
}
In practice, I wouldn't recomment using a union and dynamically switching between both views, since it makes the code quite more complicated (I've only done that to compare multiple implementations, for learning purposes). Instead, I recommend picking a view (indexed or named), and sticking with it.
If you reallyreally need to squeeze each nano-second of performance, the indexed view seems to lead to better assembly code, so it could be very marginally faster.
However, the named view leads to much more readable code, and is guaranteed to always be sound and panic-free
Why don't you just have an Attributes struct which has might, dexterity, and intelligence fields? It seems quite odd to use an array to store unrelated properties.
with struct it would be impossible to send a needed attribute as an argument to a function. i also came up with an idea to use a hashmap with string keys, because my attribute names will be stored in a database as strings, so i don’t need enum anymore.