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/miscQuick Start
package mainimport "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 hashhash, err := misc.PasswordHash("mypassword")if err != nil { // Handle error}// Verify passwordok := 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-1sha1Hash := 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 parametersinitFunc := misc.Wrap( initDatabase, initCache, initLogger,)// Call multiple timesif err := initFunc(); err != nil { log.Fatal(err)}// WrapG - with parametersprocessUser := misc.WrapG( validateUser, saveUser, notifyUser,)// Use multiple timesfor _, 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 usageresult, err := misc.Substitute("Hello {name}!", map[string]any{ "name": "World",})// result: "Hello World!"// Multiple placeholdersresult, _ := 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 unchangedresult, _ := misc.Substitute( "Hello {name}, {missing}", map[string]any{"name": "World"},)// result: "Hello World, {missing}"Interpolate - Custom Delimiters
Use custom start and end markers:
// Use {{ }} delimitersresult, _ := misc.Interpolate( "/user/{{ID}}/posts/{{PostID}}", "{{", "}}", map[string]any{ "ID": 123, "PostID": 456, },)// result: "/user/123/posts/456"// Use [[ ]] delimitersresult, _ := misc.Interpolate( "SELECT * FROM users WHERE id = [[id]]", "[[", "]]", map[string]any{"id": 42},)// result: "SELECT * FROM users WHERE id = 42"// Wildcard "*" as default valueresult, _ := 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 functiontagFunc := 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: HELLO4. Generic Utility Functions
Zero - Get Zero Value
Returns the zero value of any type:
import "go-slim.dev/misc"zeroInt := misc.Zero[int]() // 0zeroStr := misc.Zero[string]() // ""zeroBool := misc.Zero[bool]() // falsezeroPtr := misc.Zero[*int]() // nilzeroSlice := misc.Zero[[]int]() // nilzeroMap := misc.Zero[map[string]int]() // nil// Use in generic functionsfunc 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 typesintPtr := misc.Ptr(42) // *intstrPtr := misc.Ptr("hello") // *stringboolPtr := misc.Ptr(true) // *bool// Use in struct literalstype Config struct { Port *int Host *string Enabled *bool}cfg := Config{ Port: misc.Ptr(8080), Host: misc.Ptr("localhost"), Enabled: misc.Ptr(true),}// API call scenariosupdateUser(&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 functionsfunc 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) // truemisc.IsZero("") // truemisc.IsZero(false) // truemisc.IsZero((*int)(nil)) // truemisc.IsZero([]int(nil)) // truemisc.IsZero(map[string]int(nil)) // truemisc.IsZero(42) // falsemisc.IsZero("hello") // falsemisc.IsZero(true) // falsemisc.IsZero(&User{}) // false (pointer not nil)// Pointer is recursively dereferencedptr := new(int) // *int pointing to 0misc.IsZero(ptr) // true (dereferenced value is 0)*ptr = 42misc.IsZero(ptr) // false (dereferenced value is 42)IsNil - Nil Check
Check if value is nil (applicable to reference types):
misc.IsNil(nil) // truemisc.IsNil((*int)(nil)) // truemisc.IsNil([]int(nil)) // truemisc.IsNil(map[string]int(nil)) // truemisc.IsNil((chan int)(nil)) // truemisc.IsNil((func())(nil)) // truemisc.IsNil(0) // falsemisc.IsNil("") // falsemisc.IsNil(make([]int, 0)) // false (empty slice, not nil)misc.IsNil(make(map[string]int)) // false (empty map, not nil)// Interface valuesvar err error = nilmisc.IsNil(err) // trueerr = fmt.Errorf("error")misc.IsNil(err) // falseCoalesce - Get First Non-Zero Value
Returns the first non-zero value in the parameter list:
// String default valuename := misc.Coalesce("", "default", "fallback")// name = "default"// Integer default valueport := misc.Coalesce(0, 8080, 3000)// port = 8080// Configuration priority: CLI > environment variable > config file > defaultport := misc.Coalesce( cliPort, // CLI argument envPort, // Environment variable configPort, // Config file 8080, // Default)// User input fallbackusername := misc.Coalesce( userInput, savedUsername, "guest",)// When all are zero values, return zero valueresult := misc.Coalesce[int]() // 0result := misc.Coalesce("", "", "") // ""5. Math Functions
MinMax - Get Min and Max Simultaneously
Returns the minimum and maximum of two values:
import "go-slim.dev/misc"// Integermin, max := misc.MinMax(5, 3)// min = 3, max = 5// Floatmin, 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 valuesmin, max := misc.MinMax(10, 10)// min = 10, max = 10// Practical applicationfunc normalizeRange(a, b int) (start, end int) { return misc.MinMax(a, b)}start, end := normalizeRange(100, 50)// start = 50, end = 100Clamp - Constrain Value in Range
Constrains value within [min, max] range:
// Basic usageresult := 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 reversalresult := misc.Clamp(15, 20, 10) // 15 (auto-adjusts boundaries to 10, 20)// Floatopacity := misc.Clamp(1.5, 0.0, 1.0) // 1.0// Practical: constrain percentagefunc setVolume(vol int) int { return misc.Clamp(vol, 0, 100)}// Constrain color valuefunc normalizeColor(c int) int { return misc.Clamp(c, 0, 255)}// Constrain coordinatesx := 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 stringbytes := []byte("hello world")str := misc.BytesToString(bytes)// Zero-copy conversion, shares underlying data// String to byte slicestr := "hello world"bytes := misc.StringToBytes(str)// Zero-copy conversion, shares underlying data⚠️ Important Warnings:
- Don’t modify source data: Converted values share underlying memory, modification causes undefined behavior
- Don’t modify result:
StringToBytesreturns a read-only slice - Lifetime: Ensure source data remains valid while using result
Correct Usage:
// ✅ Correct: read-only usebytes := []byte("hello")str := misc.BytesToString(bytes)fmt.Println(str) // Safe// ✅ Correct: temporary useif misc.BytesToString(data) == "expected" { // Safe}// ❌ Wrong: modify source databytes := []byte("hello")str := misc.BytesToString(bytes)bytes[0] = 'H' // Undefined behavior! str may change// ❌ Wrong: modify resultstr := "hello"bytes := misc.StringToBytes(str)bytes[0] = 'H' // Undefined behavior! May crash or corrupt memoryUse Cases:
// Performance-sensitive string comparisonfunc 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 optimizationfunc parseField(data []byte) string { // data obtained from pool, returned to pool immediately after use return misc.BytesToString(data) // Dangerous! data may be reused}// Safer approachfunc 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 typeext := misc.ExtensionByType("image/png") // ".png"ext := misc.ExtensionByType("video/mp4") // ".mp4"// TypeByExtension - Get MIME type from extensionmimeType := 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 linestrace := misc.Stack(0) // 0 means start from callerfmt.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 registrationfunc 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 loginfunc 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}// Usagerouter := &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 emailwelcomeEmail := &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 processingtype 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 controltype 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 exampleok, 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 hashingfunc MD5(str string) string // Returns uppercase hexfunc Sha1(str string) string // Returns lowercase hexfunc Sha256(str string) string // Returns lowercase hexFunction Composition
func Call(fns ...func() error) errorfunc CallG[T any](val T, fns ...func(T) error) errorfunc Wrap(fns ...func() error) func() errorfunc WrapG[T any](fns ...func(T) error) func(val T) errorTemplate 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]() Tfunc Ptr[T any](x T) *Tfunc Nil[T any]() *Tfunc IsZero[T any](v T) boolfunc IsNil(x any) boolfunc Coalesce[T any](values ...T) TMath Functions
func MinMax[T cmp.Ordered](a, b T) (T, T)func Clamp[T cmp.Ordered](val, minT, maxT T) TZero-Copy Conversion
func BytesToString(b []byte) stringfunc StringToBytes(s string) []byteMIME Types (Deprecated)
func ExtensionByType(mimeType string) stringfunc TypeByExtension(ext string) stringfunc CharsetByType(typ string) stringStack Trace (Deprecated)
func Stack(skip int) stringNotes
-
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
-
Hash Function Selection:
- Security scenarios (passwords, signatures): Use
PasswordHashor 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
- Security scenarios (passwords, signatures): Use
-
Zero-Copy Conversion Risks:
BytesToStringandStringToBytesuse 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
-
IsZero vs IsNil:
IsZero: Checks if it’s the type’s zero value (like 0, "", false, nil)IsNil: Only checks if reference type is nilIsZerorecursively dereferences pointers
-
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
-
Function Composition Order:
CallandCallGexecute functions in order- Stops at first error
- Subsequent functions won’t execute
-
Template Interpolation Security:
Substitutedoesn’t perform HTML escaping- Need to escape manually if using in HTML
- Avoid directly inserting untrusted input into templates
-
Clamp Boundary Handling:
- Automatically handles min > max case (will swap boundaries)
- No need to manually check boundary order before use
Performance Recommendations
- Password Hashing: Process asynchronously, avoid blocking main flow
- Zero-Copy Conversion: Only use in performance-critical paths, mind security
- Function Composition: Avoid using
Wrapin loops, should create outside loop - Template Interpolation: Consider caching results or using
text/templatefor frequent use
Related Links
- GitHub Repository
- bcrypt Package
- filetype Library (recommended for MIME type detection)