Type-safe reactive state management for Go, inspired by Angular Signals
A modern, production-grade reactive programming library for Go 1.25+ that brings Angular's powerful signals pattern to the Go ecosystem with full type safety, zero allocations in hot paths, and comprehensive concurrency support.
- Pure Go - No dependencies, works everywhere Go works
- Type-Safe - Full generic support with Go 1.25+ type parameters
- Thread-Safe - Built-in synchronization for concurrent access
- Zero Allocations - Hot paths designed for zero heap allocations
- Angular-Inspired - API design inspired by Angular Signals
- Fine-Grained Reactivity - Only re-compute what changed
- Glitch-Free - Atomic updates prevent intermediate states
- Lazy Evaluation - Computed values calculate only when needed
- Effect Cleanup - Automatic resource management with cleanup callbacks
- Production Ready - 69 tests, 93.8% coverage, 28 benchmarks
go get github.com/coregx/signalsRequires Go 1.25 or later.
package main
import (
"fmt"
"github.com/coregx/signals"
)
func main() {
// Create a reactive signal
count := signals.New(0)
// Create computed value with explicit dependencies
doubled := signals.Computed(func() int {
return count.Get() * 2
}, count.AsReadonly())
// Create effect that runs immediately and on dependency changes
eff := signals.Effect(func() {
fmt.Printf("Count: %d, Doubled: %d\n", count.Get(), doubled.Get())
}, count.AsReadonly(), doubled)
defer eff.Stop()
// Output: Count: 0, Doubled: 0
// Update signal - effect automatically re-runs
count.Set(5)
// Output: Count: 5, Doubled: 10
count.Set(10)
// Output: Count: 10, Doubled: 20
}// Multiple dependencies
firstName := signals.New("John")
lastName := signals.New("Doe")
fullName := signals.Computed(func() string {
return firstName.Get() + " " + lastName.Get()
}, firstName.AsReadonly(), lastName.AsReadonly())
// Effect with cleanup
eff := signals.EffectWithCleanup(func() func() {
fmt.Println("Full name:", fullName.Get())
// Return cleanup function (runs before next execution and on Stop)
return func() {
fmt.Println("Effect cleaned up")
}
}, fullName)
firstName.Set("Jane") // Cleanup runs, then effect re-runs
lastName.Set("Smith") // Cleanup runs, then effect re-runs
eff.Stop() // Final cleanup runs, effect stops- API Reference - Complete API documentation
- Examples - Working code examples
- Angular-Inspired - API design mirrors Angular Signals (see Compatibility table)
- Explicit Dependencies - Go requires explicit dependency passing (no auto-tracking like Angular)
- Zero Allocation Hot Paths - Get/Set/Execute paths are allocation-free
Version: v0.1.1 (unreleased)
Production Readiness: Core functionality complete and stable!
- Signal creation and basic operations
- Thread-safe read/write with RWMutex
- Subscription system with automatic unsubscribe
- Context-based auto-cancellation
- Panic recovery with custom handlers
- Read-only view support
- Custom equality functions
- Comprehensive test coverage (19 tests)
- Zero allocations in hot paths (verified by benchmarks)
- Lazy evaluation with automatic caching
- Explicit dependency tracking and invalidation
- Fine-grained reactivity
- Glitch-free execution
- Thread-safe recomputation
- Comprehensive test coverage (17 tests)
- Optimized performance (minimal allocations)
- Explicit dependency tracking
- Cleanup function support (runs before re-execution and on Stop)
- Panic recovery with custom handlers
- Immediate execution on creation (Angular pattern)
- Comprehensive test coverage (16 tests)
- Concurrent effect management
| Package | Tests | Coverage | Benchmarks |
|---|---|---|---|
| signals | 69 | 93.8% | 28 |
Key Metrics:
- 19 Signal tests
- 17 Computed tests
- 16 Effect tests
- 3 Internal tests (type erasure, reflection fallback)
- 10 Allocation regression tests (
testing.AllocsPerRun) - 2 Subscribe lifecycle regression tests (goroutine leak, race condition)
- 2 Readonly cache tests
- Zero allocations in signal read/write hot paths
- Race detector clean (all tests pass with
-racein CI)
BenchmarkSignal_Get 96773490 13.46 ns/op 0 B/op 0 allocs/op
BenchmarkSignal_Set 20975720 52.42 ns/op 0 B/op 0 allocs/op
BenchmarkSignal_Subscribe 17057289 134 ns/op 52 B/op 2 allocs/op
BenchmarkComputed_Get_Clean 96773490 13.46 ns/op 0 B/op 0 allocs/op
BenchmarkEffect_Execute 8426302 136 ns/op 0 B/op 0 allocs/op
BenchmarkEffect_Create 7097109 324 ns/op 196 B/op 7 allocs/op
Zero allocations in hot paths (Get, Set, Execute). Subscribe uses context.AfterFunc — no goroutine per subscription.
- User guides and tutorials
- API documentation examples
- Migration guides
- Best practices guide
- Resource tracking and lifecycle management
- Advanced batching strategies
- Performance monitoring and debugging tools
- Additional utility functions
See ROADMAP.md for detailed timeline.
- Go 1.25 or later
- golangci-lint (for linting)
- No external runtime dependencies
# Clone repository
git clone https://github.com/coregx/signals.git
cd signals
# Run all tests
go test -v ./...
# Run with race detector (Linux/macOS, or CI)
go test -v -race ./...
# Run with coverage
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out
# Run benchmarks
go test -bench=. -benchmem ./...
# Run linter
golangci-lint runContributions are welcome! This is an early-stage project and we'd love your help.
Before contributing:
- Read CONTRIBUTING.md - Development workflow and guidelines
- Check open issues
- Review the API Reference
Ways to contribute:
- Report bugs
- Suggest features
- Improve documentation
- Submit pull requests
- Star the project
| Feature | Signals | RxGo | Reactor |
|---|---|---|---|
| Type-Safe Generics | Yes (Go 1.25+) | Limited | No |
| Zero Allocations | Yes (hot paths) | No | No |
| Thread-Safe | Yes (built-in) | Yes | Partial |
| Angular-Inspired | Yes | No | No |
| Fine-Grained Reactivity | Yes | Observable-based | Stream-based |
| Dependencies | Zero | Multiple | Multiple |
| Learning Curve | Low (if you know Angular) | Medium | Medium |
This library is designed to be conceptually compatible with Angular Signals:
| Angular Signals | Go Signals | Status |
|---|---|---|
signal(T) |
New[T](value) |
Complete |
computed(() => T) |
Computed[T](fn, deps...) |
Complete |
effect(() => {}) |
Effect(fn, deps...) |
Complete |
signal.set(value) |
signal.Set(value) |
Complete |
signal() |
signal.Get() |
Complete |
signal.update(fn) |
signal.Update(fn) |
Complete |
signal.asReadonly() |
signal.AsReadonly() |
Complete |
| Automatic tracking | Explicit dependencies | Adapted for Go |
| Glitch-free | Glitch-free | Complete |
| Lazy computed | Lazy computed | Complete |
This project is licensed under the MIT License - see the LICENSE file for details.
- The Angular team for the signals design pattern
- The Go team for generics and type parameters
- All contributors to this project
Status: Stable - Production-ready! Version: v0.1.1
Built with care for the Go community