DEV Community

Cover image for 🚩 Bitwise flags are amazing, and you should use them
Krypton
Krypton

Posted on • Updated on

🚩 Bitwise flags are amazing, and you should use them

Original post

If you are making a web application with users management, bitwise flags will be really useful for that same application. I've never used bitwise flags in my past small web application, and I definitely regret not having used them before... Not only it's super convenient, but you also don't need tons of rows in your database to store various account information. I'm using bitwise flags in Project Absence as well, hence this blog post explaining how it works!

PS: This will most likely be my last blog post focused on software engineering, I will focus more on cyber security in the future, as this was the main goal of my blog when I created it...

Operators Refresh

Before jumping into what bitwise flags are and how to use them, it's needed to get to know the three operators that will be used.

 OR Operator

The OR - I will also use | - is a basic operator that you most likely have already used while coding. Here's a small refresh on the result of an OR operation:

A B A OR B
0 0 0
0 1 1
1 0 1
1 1 1

AND Operator

Similar to the OR operator, the AND - I will also use & - operator is a basic operator you most likely have already used. Here's the table as well:

A B A AND B
0 0 0
0 1 0
1 0 0
1 1 1

Left Shift Operator

The left shift - I will also use << - operator is an operator you may have not used yet. For bitwise flags it is one of the base that is needed to make it working. Let's take a look at how it works, it's really easy.

Let's take as example 1 << 4:

  • We first write down 1 as binary, which is obviously 0000 0001
  • We then move that entire binary number by 4 to the left, so we are left with 0001 0000

Easy, right...? Here's a different way of showing it off:

0000 0001 << 4:
1. 0000 0010
2. 0000 0100
3. 0000 1000
4. 0001 0000
Enter fullscreen mode Exit fullscreen mode

So 1 << 4 is equals to 16. Yes, we can definitely write that down, though it gets more readable to use left shifts once you're dealing with big numbers such as 1 << 23 as you may not know that 8388608 actually represents this...

How do bitwise flags work?

All those operators are very wonderful, but what are actually bitwise flags...?

Well, very simple. It's a way to have many ON/OFF toggles in a single number. Let's quickly show that off with an example, you will also see how the OR and left shift operators will be used.

package main

import "fmt"

// Set some user account flags
const (
    EMAIL_VERIFIED = 1 << 0
    ACCOUNT_BANNED = 1 << 1
)

// Create the Account structure
type Account struct {
    Username string
    Email    string
    Flags    int // Here there will be all account flags, e.g. whether the account is currently banned
}

// We create the function to check if the account is banned
func (a *Account) IsBanned() bool {
    return (a.Flags & ACCOUNT_BANNED) == ACCOUNT_BANNED
}
Enter fullscreen mode Exit fullscreen mode

Now the, maybe, big question: "How does this even work???"

Check if flag exists

Well, checking if a flag is existent is very simple. Considering we always use 1 << X, when performing the left shift we will always and only move a binary 1 around. Then checking for a flag is as simple as using the AND operator. Let's suppose the user's account Flags field is 18.

18              =>              0001 0010
ACCOUNT_BANNED  => 1 << 1 =>    0000 0010

0001 0010
0000 0010 AND
-------------
0000 0010       -> This is equals to ACCOUNT_BANNED
Enter fullscreen mode Exit fullscreen mode

Hopefully with that example above you now understand why (a.Flags & ACCOUNT_BANNED) == ACCOUNT_BANNED can be used to check if a flag is existing.

Creating the a.Flags value

To create the value that will be needed to be stored in a.Flags is also very simple. Let's suppose our account is both verified and banned, our flag will need to contain both of these values.

EMAIL_VERIFIED  => 1 << 0 =>    0000 0001
ACCOUNT_BANNED  => 1 << 1 =>    0000 0010

0000 0001
0000 0010 OR
------------
0000 0011
Enter fullscreen mode Exit fullscreen mode

Now you most likely understand why the pattern is using 1 and not some other number in 1 << X, other numbers may overwrite other flags. Now we convert 0000 0011 to decimal and we get 3 - that is the value for a.Flags. We can always add new flags with newValue = oldValue | flag.

Why and where to use bitwise flags?

Now I understand that the explanation above is not the best, I may edit the post based on comments to clarify some things if needed - but maybe with some examples when bitwise flags may be useful, you may understand it better.

Roles

Without bitwise flags

Let's suppose you need roles in your web application, totally understandable. How would you do it?

Maybe an ENUM field in your database if an account can only have one role. But what if it can have more than one? Maybe create a table roles that looks like the following:

user_id administrator moderator member
1337 1 1 1
7331 0 1 1
7337 0 0 1

You get where this is going... Horrible table with just a row per role, yikes.

With bitwise flags

With bitwise flags, you just need a single row roles in your user's table :) Then you assign for every role a left shift operation:

const (
    ADMINISTRATOR = 1 << 0
    MODERATOR     = 1 << 1
    MEMBER        = 1 << 2
)
Enter fullscreen mode Exit fullscreen mode

After that use the techniques above to create the value for roles, for the user 7331 for example: roles = MODERATOR | MEMBER. Then we can check if a user is an administrator with something like that:

func (u *User) IsAdministrator() bool {
    return (u.Roles & ADMINISTRATOR) == ADMINISTRATOR
}
Enter fullscreen mode Exit fullscreen mode

Same goes for other account flags as shown above, to know if an account has the email verified or if they are banned.

Permissions

Same as roles, if you need different users to have permissions on different functionalities of your web application, it may be totally worth it using bitwise flags.

Remove unnecessary functions

Now this one may not be the biggest strength of bitwise flags, but it can definitely be used to optimize some functions in your code. Yes, functions.

 Without bitwise flags

Let's imagine we want to print some information about a human:

package main

import "fmt"

type Human struct {
    Firstname string
    Lastname  string
    Username  string
    Email     string
    Age       int
}

func (h *Human) PrintData() {
    fmt.Println("Firstname:", h.Firstname, "Lastname:", h.Lastname, "Username:", h.Username, "Email:", h.Email, "Age:", h.Age)
}

func main() {
    myHuman := &Human{
        Firstname: "John",
        Lastname:  "Doe",
        Username:  "jdoe",
        Email:     "john@doe.com",
        Age:       1337, // Yes that human it quite old...
    }
    myHuman.PrintData()
}
Enter fullscreen mode Exit fullscreen mode

Now this works, as we get the expected output. But what if we want to print a single field? What if you don't want to print all of the fields? Well, yes you can do the following for all of your fields

func (h *Human) PrintFirstname() {
    fmt.Println("Firstname:", h.Firstname)
}
Enter fullscreen mode Exit fullscreen mode

But here again, what if you want only 3 of them? Well, you can replace Println to Print and call the 3 functions to print those fields. But cool kids use bitwise flags 🎉

With bitwise flags

Here again, assign each field a left shift operation and we copy the structure of above:

package main

import "fmt"

type Human struct {
    Firstname string
    Lastname  string
    Username  string
    Email     string
    Age       int
}

const (
    FIRSTNAME = 1 << 0
    LASTNAME  = 1 << 1
    USERNAME  = 1 << 2
    EMAIL     = 1 << 3
    AGE       = 1 << 4
)
Enter fullscreen mode Exit fullscreen mode

And now we can create the function needed to print each field, depending on the parameter - the flags - we give to the function.

func (h *Human) PrintDataWithFlag(flags int) {
    // The " " at the end is just to make sure it looks good when printing multiple things
    if (flags & FIRSTNAME) == FIRSTNAME {
        fmt.Print("Firstname: ", h.Firstname, " ")
    }
    if (flags & LASTNAME) == LASTNAME {
        fmt.Print("Lastname: ", h.Lastname, " ")
    }
    if (flags & USERNAME) == USERNAME {
        fmt.Print("Username: ", h.Username, " ")
    }
    if (flags & EMAIL) == EMAIL {
        fmt.Print("Email: ", h.Email, " ")
    }
    if (flags & AGE) == AGE {
        fmt.Print("Age: ", h.Age, " ")
    }
}
Enter fullscreen mode Exit fullscreen mode

Now what if we want to just print the Firstname, the Email and the Age fields? Very simple:

func main() {
    myHuman := &Human{
        Firstname: "John",
        Lastname:  "Doe",
        Username:  "jdoe",
        Email:     "john@doe.com",
        Age:       1337, // Yes that human it quite old...
    }
    myHuman.PrintDataWithFlag(FIRSTNAME | EMAIL | AGE)
}
Enter fullscreen mode Exit fullscreen mode

Now we have a clean way of printing all the 3 fields, without calling a different function 3 times. You can see the full code here.

Conclusion

If you've mangaged to read and understand all the way down until here and haven't given up on my poor explanation, congrats! Bitwise flags are somewhat complicated for newcomers but also easy once you understood the basics of it and where/how to use them. I personally use bitwise fields for roles and account flags in Project Absence. I don't really like having multiple rows in my datbase just to store such information, I'd rather have a single fields row.

Top comments (0)