When you write code, you may need to create different outcomes each time it's executed. One way to achieve this is by using random numbers. Random numbers are sequences of numbers that don't follow a specific order. Random numbers have many uses in programming, such as in blockchain applications for digital encryption or simulation projects to generate data. Incorporating random numbers into your code makes it more versatile and adaptable to various scenarios.
This topic will focus on working with random numbers in Go, covering their generation and how to use them to create random strings.
Generating random integers and floats
In Go, there is a package math/rand that allows the generation of random integers and floating numbers. To do so, two commands are used: rand.Int() and rand.Intn() . The difference is that the former does not accept any arguments while the latter takes only one argument, which shows the upper bound of the desired range. The thing that should be noted is that the lower bound in the generating random numbers is 0, making the range of random number generation [0, n), where n is the upper bound. Let's see it in practice.
package main
import (
"fmt"
"math/rand"
)
func main() {
fmt.Println(rand.Int())
fmt.Println(rand.Intn(20))
}
If you run the code, you'll see that the first output is a large integer, while the second is within the range of [0, 20). When you run the code again, you'll notice that the same numbers are generated. This happens because the same seed number is used each time. To avoid this behavior, you can change the seed number every time the code is executed. The time package is often used to achieve this. By adding the line rand.Seed(time.Now().UnixNano()) before calling rand.Int(), you can ensure that a different seed number is used each time, resulting in different random numbers being generated. Try importing the time package and adding this line of code to your program. You should now see different results each time you run the program, demonstrating how random integers can be generated in Go.
math/rand package automatically seeds the global random number generator, making it random by default. So, If you're using Go 1.20 or later, there's no need to call rand.Seed(). You can read the Go 1.20 release notes for more information about these changes.The way of generating random floating numbers is the same. rand.Float64() generates random floating numbers in the interval of [0.0, 1.0). This function does not accept any arguments; therefore, some mathematical operations should be added if we want to change the intervals. For example, 5 + rand.Float64() * 10 will generate floating numbers in the interval of [5, 15).
Alternative package to generate random numbers
The math/rand package is useful for generating random numbers in applications that do not require high levels of security. However, it should not be used in projects where security is a critical concern. This is because the generated numbers can be easily predicted since the sequence of random numbers is deterministic. This means that future values can be predicted from past outputs. Instead, the crypto/rand package should be used for security-critical projects.
Here's an example of how to use the crypto/rand package to generate random numbers:
package main
import (
"fmt"
"crypto/rand"
"math/big"
)
func main() {
randomNumber, _ := rand.Int(rand.Reader, big.NewInt(100))
fmt.Println(randomNumber)
randomPrimeNumber, _ := rand.Prime(rand.Reader, 8)
fmt.Println(randomPrimeNumber)
}
Here rand.Reader is the global instance of a secure random number generator; rand.Int() generates random numbers in the interval of [0, big.NewInt(100)); rand.Prime() on the other hand, returns a random prime number with an 8-bit length. If instead of 8 any other n integer is inputted such that n > 1, then the random number would be n-bit length.
So, there are two packages that can be used to work with random numbers: math/rand and crypto/rand.
Generating random characters and strings
You now know how to generate random numbers with two different packages. With this knowledge, you can also obtain distinct characters from the charset and random strings with the specified length. Let's start with the characters.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
charset := "abcdefghijklmnopqrstuvwxyz"
randomPosition := rand.Intn(len(charset))
c := charset[randomPosition]
fmt.Println(string(c))
}
To get various letters, first, you need some kind of characters bank, and charset in the above code plays the role of the char bank. Then by generating random integers, you could access different positions in the set. However, we should not obtain a random number more than the length of our set. Therefore, rand.Intn() is used with the argument of len(charset). So that our interval lies within [0, len(charset)). And after generating a random position, you could assign a character in this random position in the character set to a new variable.
To generate random strings, you need to work with slices. This is because you need to collect random characters generated from the charset. For example, the following code will output us a random string with a length of 10.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
charset := "abcdefghijklmnopqrstuvwxyz"
length := 10
random := make([]byte, length)
for i := 0; i < length; i++ {
randomPosition := rand.Intn(len(charset))
c := charset[randomPosition]
random[i] = c
}
str := string(random)
fmt.Println(str)
}
The thing to note is that our slice is of byte type. As we know, in Go, there is no specific char data type; instead, it uses byte. Therefore, you populate our slice with the byte type. Generally speaking, byte type represents ASCII characters. This fact leads us to another way of generating random numbers.
In the ASCII table, the upper-case letters lie within [65, 90], and lower-case letters are in the interval of [97, 122]. Therefore, you need to generate random numbers in the desired interval depending on what kind of letters you want. For instance, the following code shows how upper-case and lower-case strings can be generated using ASCII.
package main
import (
"fmt"
"math/rand"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
length := 10
randomUpperCaseString := make([]byte, length)
for i := 0; i < length; i++ {
randomUpperCaseString[i] = byte(65 + rand.Intn(26))
}
str1 := string(randomUpperCaseString)
fmt.Println(str1)
randomLowerCaseString := make([]byte, length)
for i := 0; i < length; i++ {
randomLowerCaseString[i] = byte(97 + rand.Intn(26))
}
str2 := string(randomLowerCaseString)
fmt.Println(str2)
}
In the first line, the string is upper-case, while in the second, it is lower-case. The same logic can be used to generate random characters instead of using a predefined character set. Also, one thing to note:
-
In projects where random passwords are generated (which are strings), the interval of ASCII can be changed so that not only upper-case or lower-case letters lie within the range, but also numbers' and specific characters' ASCII representation can be included in the interval. The respective ASCII numbers can be found on the ASCII table
Conclusion
In this topic, you learned how random numbers are generated in Go; this random number generation can help generate random characters and even random strings of different types. The key points of this topic are:
-
The random characters in Go can be generated using
math/randorcrypto/randpackages. -
The
crypto/randpackage offers enhanced security in generating random numbers, producing truly random values. Therefore, if a developer is working on an application where security is a critical aspect, they should use this package to generate their random numbers. -
The random characters and strings can be generated using a predefined charset or using the ASCII table.