Skip to main content

misc - Utility Library

misc is a utility library providing common utility functions and types, including password hashing, function composition, MIME type handling, template interpolation, generic utilities, math functions, and more. Minimum supported Go version: 1.21.0.

Installation

go get -u go-slim.dev/misc

Quick Start

package main

import "go-slim.dev/misc"

func main() {
// Password hashing
hash, _ := misc.PasswordHash("mypassword")
ok := misc.PasswordVerify("mypassword", hash)

// Template substitution
result, _ := misc.Substitute("Hello {name}!", map[string]any{"name": "World"})

// Generic utilities
value := misc.Coalesce("", "default", "fallback") // "default"

// Math functions
min, max := misc.MinMax(5, 3) // (3, 5)
}

Core Features

1. Password Hashing and Digests

Password Hashing (Bcrypt)

Use bcrypt algorithm for secure password hashing:

import "go-slim.dev/misc"

// Generate password hash
hash, err := misc.PasswordHash("mypassword")
if err != nil {
// Handle error
}

// Verify password
ok := misc.PasswordVerify("mypassword", hash)
if ok {
// Password correct
} else {
// Password incorrect
}

Features:

  • Uses bcrypt.DefaultCost (currently 10)
  • Automatically generates salt
  • Protects against rainbow table attacks
  • Adjustable computational cost

Fast Hash Functions

For data integrity checking and non-security scenarios:

// MD5 (not recommended for security scenarios)
md5Hash := misc.MD5("hello world")
// Returns uppercase hex: "5EB63BBBE01EEED093CB22BB8F5ACDC3"

// SHA-1
sha1Hash := misc.Sha1("hello world")
// Returns lowercase hex: "2aae6c35c94fcfb415dbe95f408b9ce91ee846ed"

// SHA-256 (recommended)
sha256Hash := misc.Sha256("hello world")
// Returns lowercase hex: "b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9"

Use Cases:

  • File integrity checking
  • Cache key generation
  • Data fingerprinting
  • ETag generation

Note: MD5 and SHA-1 should not be used for security-sensitive scenarios (like password storage). Use SHA-256 or bcrypt instead.

2. Function Composition

Call - Sequential Execution

Execute functions sequentially, returns immediately on first error:

import "go-slim.dev/misc"

err := misc.Call(
func() error {
fmt.Println("Step 1")
return nil
},
func() error {
fmt.Println("Step 2")
return nil
},
func() error {
fmt.Println("Step 3")
return errors.New("failed")
},
func() error {
fmt.Println("Step 4") // Won't execute
return nil
},
)
// Output: Step 1, Step 2, Step 3
// err is "failed"

CallG - Sequential Execution with Parameters

Execute multiple functions that receive the same parameter:

type User struct {
Name string
Age int
}

user := &User{Name: "Alice", Age: 30}

err := misc.CallG(user,
func(u *User) error {
fmt.Printf("Validating user: %s\n", u.Name)
return nil
},
func(u *User) error {
fmt.Printf("Saving user: %s\n", u.Name)
return db.Save(u)
},
func(u *User) error {
fmt.Printf("Sending welcome email to: %s\n", u.Name)
return mailer.Send(u)
},
)

Wrap and WrapG - Function Wrapping

Combine multiple functions into one reusable function:

// Wrap - no parameters
initFunc := misc.Wrap(
initDatabase,
initCache,
initLogger,
)

// Call multiple times
if err := initFunc(); err != nil {
log.Fatal(err)
}

// WrapG - with parameters
processUser := misc.WrapG(
validateUser,
saveUser,
notifyUser,
)

// Use multiple times
for _, user := range users {
if err := processUser(user); err != nil {
log.Printf("Failed to process user: %v", err)
}
}

3. Template Interpolation

Substitute - Simple Template Replacement

Use {} as placeholders:

// Basic usage
result, err := misc.Substitute("Hello {name}!", map[string]any{
"name": "World",
})
// result: "Hello World!"

// Multiple placeholders
result, _ := misc.Substitute(
"User {name} is {age} years old",
map[string]any{
"name": "Alice",
"age": 30,
},
)
// result: "User Alice is 30 years old"

// Missing placeholders remain unchanged
result, _ := misc.Substitute(
"Hello {name}, {missing}",
map[string]any{"name": "World"},
)
// result: "Hello World, {missing}"

Interpolate - Custom Delimiters

Use custom start and end markers:

// Use {{ }} delimiters
result, _ := misc.Interpolate(
"/user/{{ID}}/posts/{{PostID}}",
"{{", "}}",
map[string]any{
"ID": 123,
"PostID": 456,
},
)
// result: "/user/123/posts/456"

// Use [[ ]] delimiters
result, _ := misc.Interpolate(
"SELECT * FROM users WHERE id = [[id]]",
"[[", "]]",
map[string]any{"id": 42},
)
// result: "SELECT * FROM users WHERE id = 42"

// Wildcard "*" as default value
result, _ := misc.Interpolate(
"{greeting} {name}!",
"{", "}",
map[string]any{
"name": "Alice",
"*": "Unknown", // Default value
},
)
// result: "Unknown Alice!"

Tmpl - Advanced Template Processing

Use custom TagFunc to process placeholders:

import (
"io"
"strings"
"go-slim.dev/misc"
)

var output strings.Builder

// Custom tag handler function
tagFunc := func(w io.Writer, tag string) (int, error) {
switch tag {
case "date":
return w.Write([]byte(time.Now().Format("2006-01-02")))
case "time":
return w.Write([]byte(time.Now().Format("15:04:05")))
case "upper":
// Can access context or execute complex logic
return w.Write([]byte("HELLO"))
default:
return w.Write([]byte("Unknown tag"))
}
}

n, err := misc.Tmpl(
"Date: {date}, Time: {time}, Message: {upper}",
"{", "}",
&output,
tagFunc,
)

fmt.Println(output.String())
// Output: Date: 2024-01-15, Time: 14:30:45, Message: HELLO

4. Generic Utility Functions

Zero - Get Zero Value

Returns the zero value of any type:

import "go-slim.dev/misc"

zeroInt := misc.Zero[int]() // 0
zeroStr := misc.Zero[string]() // ""
zeroBool := misc.Zero[bool]() // false
zeroPtr := misc.Zero[*int]() // nil
zeroSlice := misc.Zero[[]int]() // nil
zeroMap := misc.Zero[map[string]int]() // nil

// Use in generic functions
func GetOrDefault[T any](m map[string]T, key string) T {
if v, ok := m[key]; ok {
return v
}
return misc.Zero[T]()
}

Ptr - Create Pointer

Returns a pointer to a copy of the value:

// Basic types
intPtr := misc.Ptr(42) // *int
strPtr := misc.Ptr("hello") // *string
boolPtr := misc.Ptr(true) // *bool

// Use in struct literals
type Config struct {
Port *int
Host *string
Enabled *bool
}

cfg := Config{
Port: misc.Ptr(8080),
Host: misc.Ptr("localhost"),
Enabled: misc.Ptr(true),
}

// API call scenarios
updateUser(&User{
ID: 123,
Name: misc.Ptr("Alice"), // Only update Name field
})

Nil - Get Typed Nil Pointer

Returns a nil pointer of specific type:

var intPtr *int = misc.Nil[int]()
var strPtr *string = misc.Nil[string]()
var slicePtr *[]int = misc.Nil[[]int]()

// Use in generic functions
func MaybeValue[T any](hasValue bool, value T) *T {
if hasValue {
return &value
}
return misc.Nil[T]()
}

IsZero - Zero Value Check

Check if any value is zero (supports deep checking):

misc.IsZero(0)                  // true
misc.IsZero("") // true
misc.IsZero(false) // true
misc.IsZero((*int)(nil)) // true
misc.IsZero([]int(nil)) // true
misc.IsZero(map[string]int(nil)) // true

misc.IsZero(42) // false
misc.IsZero("hello") // false
misc.IsZero(true) // false
misc.IsZero(&User{}) // false (pointer not nil)

// Pointer is recursively dereferenced
ptr := new(int) // *int pointing to 0
misc.IsZero(ptr) // true (dereferenced value is 0)

*ptr = 42
misc.IsZero(ptr) // false (dereferenced value is 42)

IsNil - Nil Check

Check if value is nil (applicable to reference types):

misc.IsNil(nil)                    // true
misc.IsNil((*int)(nil)) // true
misc.IsNil([]int(nil)) // true
misc.IsNil(map[string]int(nil)) // true
misc.IsNil((chan int)(nil)) // true
misc.IsNil((func())(nil)) // true

misc.IsNil(0) // false
misc.IsNil("") // false
misc.IsNil(make([]int, 0)) // false (empty slice, not nil)
misc.IsNil(make(map[string]int)) // false (empty map, not nil)

// Interface values
var err error = nil
misc.IsNil(err) // true

err = fmt.Errorf("error")
misc.IsNil(err) // false

Coalesce - Get First Non-Zero Value

Returns the first non-zero value in the parameter list:

// String default value
name := misc.Coalesce("", "default", "fallback")
// name = "default"

// Integer default value
port := misc.Coalesce(0, 8080, 3000)
// port = 8080

// Configuration priority: CLI > environment variable > config file > default
port := misc.Coalesce(
cliPort, // CLI argument
envPort, // Environment variable
configPort, // Config file
8080, // Default
)

// User input fallback
username := misc.Coalesce(
userInput,
savedUsername,
"guest",
)

// When all are zero values, return zero value
result := misc.Coalesce[int]() // 0
result := misc.Coalesce("", "", "") // ""

5. Math Functions

MinMax - Get Min and Max Simultaneously

Returns the minimum and maximum of two values:

import "go-slim.dev/misc"

// Integer
min, max := misc.MinMax(5, 3)
// min = 3, max = 5

// Float
min, max := misc.MinMax(1.5, 2.7)
// min = 1.5, max = 2.7

// String (lexicographic order)
min, max := misc.MinMax("banana", "apple")
// min = "apple", max = "banana"

// Equal values
min, max := misc.MinMax(10, 10)
// min = 10, max = 10

// Practical application
func normalizeRange(a, b int) (start, end int) {
return misc.MinMax(a, b)
}

start, end := normalizeRange(100, 50)
// start = 50, end = 100

Clamp - Constrain Value in Range

Constrains value within [min, max] range:

// Basic usage
result := misc.Clamp(15, 10, 20) // 15 (within range)
result := misc.Clamp(5, 10, 20) // 10 (below minimum)
result := misc.Clamp(25, 10, 20) // 20 (above maximum)

// Auto-handle boundary reversal
result := misc.Clamp(15, 20, 10) // 15 (auto-adjusts boundaries to 10, 20)

// Float
opacity := misc.Clamp(1.5, 0.0, 1.0) // 1.0

// Practical: constrain percentage
func setVolume(vol int) int {
return misc.Clamp(vol, 0, 100)
}

// Constrain color value
func normalizeColor(c int) int {
return misc.Clamp(c, 0, 255)
}

// Constrain coordinates
x := misc.Clamp(mouseX, 0, screenWidth)
y := misc.Clamp(mouseY, 0, screenHeight)

6. Zero-Copy Conversion

Use unsafe for zero-copy string and byte slice conversion:

import "go-slim.dev/misc"

// Byte slice to string
bytes := []byte("hello world")
str := misc.BytesToString(bytes)
// Zero-copy conversion, shares underlying data

// String to byte slice
str := "hello world"
bytes := misc.StringToBytes(str)
// Zero-copy conversion, shares underlying data

⚠️ Important Warnings:

  1. Don't modify source data: Converted values share underlying memory, modification causes undefined behavior
  2. Don't modify result: StringToBytes returns a read-only slice
  3. Lifetime: Ensure source data remains valid while using result

Correct Usage:

// ✅ Correct: read-only use
bytes := []byte("hello")
str := misc.BytesToString(bytes)
fmt.Println(str) // Safe

// ✅ Correct: temporary use
if misc.BytesToString(data) == "expected" {
// Safe
}

// ❌ Wrong: modify source data
bytes := []byte("hello")
str := misc.BytesToString(bytes)
bytes[0] = 'H' // Undefined behavior! str may change

// ❌ Wrong: modify result
str := "hello"
bytes := misc.StringToBytes(str)
bytes[0] = 'H' // Undefined behavior! May crash or corrupt memory

Use Cases:

// Performance-sensitive string comparison
func fastCompare(a []byte, b string) bool {
return misc.BytesToString(a) == b
}

// HTTP header parsing (read-only)
func parseHeader(line []byte) (key, value string) {
parts := bytes.SplitN(line, []byte(":"), 2)
return misc.BytesToString(parts[0]),
misc.BytesToString(bytes.TrimSpace(parts[1]))
}

// JSON parsing optimization
func parseField(data []byte) string {
// data obtained from pool, returned to pool immediately after use
return misc.BytesToString(data) // Dangerous! data may be reused
}

// Safer approach
func parseFieldSafe(data []byte) string {
return string(data) // Standard conversion, creates copy
}

7. MIME Type Handling

Deprecated: Recommend using github.com/h2non/filetype library.

// ExtensionByType - Get extension from MIME type
ext := misc.ExtensionByType("image/png") // ".png"
ext := misc.ExtensionByType("video/mp4") // ".mp4"

// TypeByExtension - Get MIME type from extension
mimeType := misc.TypeByExtension(".jpg") // "image/jpeg"
mimeType := misc.TypeByExtension("png") // "image/png" (can omit dot)

// CharsetByType - Get charset (limited functionality)
charset := misc.CharsetByType("text/html") // "charset=utf-8"
charset := misc.CharsetByType("image/png") // ""

8. Stack Trace

Deprecated: Recommend using runtime.Stack or debug.Stack.

// Stack - Get stack trace with source lines
trace := misc.Stack(0) // 0 means start from caller
fmt.Println(trace)

Use Cases

1. User Authentication in Web Applications

import (
"go-slim.dev/misc"
"go-slim.dev/slim"
)

type User struct {
Username string
Password string
}

// User registration
func registerUser(c slim.Context) error {
var user User
if err := c.Bind(&user); err != nil {
return err
}

// Generate password hash
hash, err := misc.PasswordHash(user.Password)
if err != nil {
return c.String(500, "Password encryption failed")
}

// Save to database
user.Password = hash
if err := db.Save(&user); err != nil {
return c.String(500, "Save failed")
}

return c.String(200, "Registration successful")
}

// User login
func loginUser(c slim.Context) error {
var input User
if err := c.Bind(&input); err != nil {
return err
}

// Get user from database
var user User
if err := db.FindByUsername(input.Username, &user); err != nil {
return c.String(401, "Incorrect username or password")
}

// Verify password
if !misc.PasswordVerify(input.Password, user.Password) {
return c.String(401, "Incorrect username or password")
}

// Generate token and return
token := generateToken(user)
return c.JSON(200, map[string]string{"token": token})
}

2. Configuration Management

import (
"os"
"strconv"
"go-slim.dev/misc"
)

type Config struct {
Host string
Port int
Database string
Debug bool
}

func loadConfig() *Config {
// Use Coalesce to implement configuration priority
// CLI > environment variable > default

portStr := misc.Coalesce(
os.Getenv("APP_PORT"),
"8080",
)
port, _ := strconv.Atoi(portStr)

return &Config{
Host: misc.Coalesce(
os.Getenv("APP_HOST"),
"localhost",
),
Port: port,
Database: misc.Coalesce(
os.Getenv("DATABASE_URL"),
"postgres://localhost/myapp",
),
Debug: os.Getenv("DEBUG") == "true",
}
}

3. URL Routing Templates

import "go-slim.dev/misc"

type Router struct {
baseURL string
}

func (r *Router) UserProfile(userID int) string {
url, _ := misc.Substitute(
r.baseURL+"/users/{id}/profile",
map[string]any{"id": userID},
)
return url
}

func (r *Router) UserPosts(userID, page int) string {
url, _ := misc.Interpolate(
r.baseURL+"/users/{{userID}}/posts?page={{page}}",
"{{", "}}",
map[string]any{
"userID": userID,
"page": page,
},
)
return url
}

// Usage
router := &Router{baseURL: "https://api.example.com"}
profileURL := router.UserProfile(123)
// "https://api.example.com/users/123/profile"

postsURL := router.UserPosts(123, 2)
// "https://api.example.com/users/123/posts?page=2"

4. Data Validation Pipeline

import "go-slim.dev/misc"

type Order struct {
ID int
UserID int
Amount float64
Status string
}

func validateOrder(order *Order) error {
return misc.Call(
func() error {
if order.UserID <= 0 {
return errors.New("Invalid user ID")
}
return nil
},
func() error {
if order.Amount <= 0 {
return errors.New("Amount must be greater than 0")
}
return nil
},
func() error {
if order.Status == "" {
return errors.New("Status cannot be empty")
}
return nil
},
)
}

func processOrder(order *Order) error {
return misc.CallG(order,
validateOrder,
saveOrder,
sendConfirmation,
updateInventory,
)
}

5. Email Templates

import "go-slim.dev/misc"

type EmailTemplate struct {
template string
}

func (t *EmailTemplate) Render(data map[string]any) (string, error) {
return misc.Substitute(t.template, data)
}

// Welcome email
welcomeEmail := &EmailTemplate{
template: `
Dear {name},

Welcome to {company}!

Your account has been successfully created:
- Username: {username}
- Email: {email}

Please visit {url} to complete activation.

Best regards,
{company} Team
`,
}

content, _ := welcomeEmail.Render(map[string]any{
"name": "Alice",
"company": "TechCorp",
"username": "alice123",
"email": "alice@example.com",
"url": "https://example.com/activate",
})

6. Input Range Limiting

import "go-slim.dev/misc"

// Image processing
type Image struct {
Width int
Height int
}

func (img *Image) SetBrightness(value float64) {
// Limit brightness between 0.0 and 2.0
img.brightness = misc.Clamp(value, 0.0, 2.0)
}

func (img *Image) SetPixel(x, y int, color RGB) {
// Limit coordinates within image bounds
x = misc.Clamp(x, 0, img.Width-1)
y = misc.Clamp(y, 0, img.Height-1)

// Limit color values to 0-255 range
r := misc.Clamp(color.R, 0, 255)
g := misc.Clamp(color.G, 0, 255)
b := misc.Clamp(color.B, 0, 255)

img.setPixelUnsafe(x, y, RGB{r, g, b})
}

// Volume control
type AudioPlayer struct {
volume int
}

func (p *AudioPlayer) SetVolume(vol int) {
p.volume = misc.Clamp(vol, 0, 100)
}

func (p *AudioPlayer) IncreaseVolume(delta int) {
p.volume = misc.Clamp(p.volume+delta, 0, 100)
}

7. File Integrity Checking

import (
"io"
"os"
"go-slim.dev/misc"
)

func calculateFileHash(filename string) (string, error) {
data, err := os.ReadFile(filename)
if err != nil {
return "", err
}

// Use SHA-256 to calculate file hash
return misc.Sha256(string(data)), nil
}

func verifyFileIntegrity(filename, expectedHash string) (bool, error) {
actualHash, err := calculateFileHash(filename)
if err != nil {
return false, err
}

return actualHash == expectedHash, nil
}

// Usage example
ok, err := verifyFileIntegrity("download.zip", "b94d27b99...")
if !ok {
log.Fatal("File corrupted or tampered with")
}

API Reference

Password and Digests

// Password hashing (bcrypt)
func PasswordHash(password string) (string, error)
func PasswordVerify(password, hash string) bool

// Fast hashing
func MD5(str string) string // Returns uppercase hex
func Sha1(str string) string // Returns lowercase hex
func Sha256(str string) string // Returns lowercase hex

Function Composition

func Call(fns ...func() error) error
func CallG[T any](val T, fns ...func(T) error) error
func Wrap(fns ...func() error) func() error
func WrapG[T any](fns ...func(T) error) func(val T) error

Template Interpolation

func Substitute(template string, data map[string]any) (string, error)
func Interpolate(template, startTag, endTag string, data map[string]any) (string, error)
func Tmpl(template, startTag, endTag string, w io.Writer, f TagFunc) (int64, error)

type TagFunc func(w io.Writer, tag string) (int, error)

Generic Utilities

func Zero[T any]() T
func Ptr[T any](x T) *T
func Nil[T any]() *T
func IsZero[T any](v T) bool
func IsNil(x any) bool
func Coalesce[T any](values ...T) T

Math Functions

func MinMax[T cmp.Ordered](a, b T) (T, T)
func Clamp[T cmp.Ordered](val, minT, maxT T) T

Zero-Copy Conversion

func BytesToString(b []byte) string
func StringToBytes(s string) []byte

MIME Types (Deprecated)

func ExtensionByType(mimeType string) string
func TypeByExtension(ext string) string
func CharsetByType(typ string) string

Stack Trace (Deprecated)

func Stack(skip int) string

Notes

  1. Password Hashing Performance:

    • bcrypt is intentionally slow to prevent brute-force attacks
    • Don't call frequently in performance-sensitive code paths
    • Default cost is 10, each increment doubles the time
  2. Hash Function Selection:

    • Security scenarios (passwords, signatures): Use PasswordHash or SHA-256
    • Non-security scenarios (cache keys, ETag): Can use MD5 or SHA-1
    • MD5 and SHA-1 should not be used for password storage
  3. Zero-Copy Conversion Risks:

    • BytesToString and StringToBytes use unsafe
    • Share underlying memory, modifying source data causes undefined behavior
    • Only use when certain data won't be modified
    • Significant performance improvement but requires caution
  4. IsZero vs IsNil:

    • IsZero: Checks if it's the type's zero value (like 0, "", false, nil)
    • IsNil: Only checks if reference type is nil
    • IsZero recursively dereferences pointers
  5. Coalesce Behavior:

    • Returns first non-zero value, not first non-nil value
    • Empty string "" is zero value, will be skipped
    • Empty slice []int{} is not zero value, will be returned
  6. Function Composition Order:

    • Call and CallG execute functions in order
    • Stops at first error
    • Subsequent functions won't execute
  7. Template Interpolation Security:

    • Substitute doesn't perform HTML escaping
    • Need to escape manually if using in HTML
    • Avoid directly inserting untrusted input into templates
  8. Clamp Boundary Handling:

    • Automatically handles min > max case (will swap boundaries)
    • No need to manually check boundary order before use

Performance Recommendations

  1. Password Hashing: Process asynchronously, avoid blocking main flow
  2. Zero-Copy Conversion: Only use in performance-critical paths, mind security
  3. Function Composition: Avoid using Wrap in loops, should create outside loop
  4. Template Interpolation: Consider caching results or using text/template for frequent use