Reload Go code in a running process at function/method level granularity

got reload?

Function/method-level stateful hot reloading for Go!

Status

Very much work in progress. The usage of this tool changes pretty much daily as we iterate on it. That being said, it is usually usable for some definition of "usable."

Do you have a demo?

Note: We intend to simplify the usage greatly from this form; bear with us!

Clone this repo somewhere and do the following:

# define a directory for rewritten source code
export GOT_RELOAD_TREE=$(mktemp -d)

# define the packages we want to make reloadable
export GOT_RELOAD_PKGS=$(echo github.com/got-reload/got-reload/demo/{example,example2} | tr ' ' ,)

# define the location of our main package's source code
export GOT_RELOAD_SOURCE_DIR=$(cd demo && pwd)

# copy all of our files to our alternative tree to be rewritten
tar -cf - * | tar -xf - -C "$GOT_RELOAD_TREE"

# rewrite those files to be reloadable
go run ./cmd/got-reload/ filter -dir "$GOT_RELOAD_TREE" $(echo "$GOT_RELOAD_PKGS" | tr , ' ')

# signal the live reloader to activate when its init() function is called
export GOT_RELOAD_START_RELOADER=1

# go to our rewritten main package
cd "$GOT_RELOAD_TREE/demo"

# run our code
go run -v .

# press enter a few times to see the method get invoked and to watch the
# package-level variable get incremented

In a different terminal, return to the original cloned repo and edit one of the function definitions in demo/example or demo/example2. For starters, just make it return a different constant.

You should see the running program discover the changes and reload the definition of the function. Press enter a few more times to watch the return value change. Note how the package-level variable's state was not reset by the reload.

Inspiration

See this video that Chris did for something similar:

https://user-images.githubusercontent.com/2324697/106301108-4e2fce80-6225-11eb-8038-1d726b3eb269.mp4

How it works

Rewrite each function/method

We alter each function and method declaration in your code so that it invokes a package-level function variable. This allows us to redefine the implementation of your functions/methods at runtime.

The filter will transparently change functions from this

func Foo(... args ...) (...return values...) {
  // body
}

into this

func Foo(... args ...) (...return values...) {
  return GRLf_Foo(...args...)
}

var GRLf_Foo = func(...args...) (...return values...) {
   // body
}

func GRLset_Foo(f func(...Foo's signature)...) {
  GRLf_Foo = f
}

and similarly for methods.

Export all named private package-level variables, types, interfaces, and struct field names, by adding "GRL_" to the front.

(None of this is done in-place, it's all performed on a temporary copy of the packages being filtered. No original source code is changed.)

We watch your source for changes at runtime

When a filtered source file changes, it will be read, parsed, and changed functions will be installed with new versions of themselves via the generated GRLset_* functions, via Yaegi, a Go interpreter.

Limitations

  • Fundamental limitations

    • Does not support reloading packages that directly reference CGO symbols. You can still depend on packages that use CGO, just don't use any C.foo symbols in your reloadable code.
    • Cannot redefine functions that never return. If your whole program runs an event loop that iterates indefinitely over some channels, the new definition of that event loop function will never be invoked because the old one never returned.
    • Cannot redefine main or init functions (even if you could, it would have no effect. Your program has already started, so these functions have already executed.)
  • Current practical limitations (things we hope to eventually work around)

    • You cannot change function signatures.
    • You cannot redefine types (add/remove/change fields).
    • You cannot add new package-scope variables or constants during a reload (this should be easy to fix, just haven't gotten to it).
    • You cannot gain new module dependencies during a reload. That said, you can import any package that your module already imports transitively. So if X imports Y and you only import X, then you can later import Y without issue. You can also import any package in the standard library, which is already built-in to Yaegi.
    • You cannot reload any symbols in the main package. You can work around this by just copying your current main code to (for example) grl_main, exporting main as Main, and rewriting your real main to just call grl_main.Main(). Eventually we'll teach the filter how to do this for you. (Issue 5)

Who came up with this harebrained idea?

Given that Yaegi's been out for a while, and Go's parsing tools have been out since the beginning (well, a lot of them, anyway), we both wonder why nobody has done this yet, to be honest.

Can I use it now?

Yes? Kinda depends on your tolerance for jank and breaking changes. If you can survive the fact that the CLI may change on a daily basis, then sure!

Can I support the development of this tool?

Yes! We appreciate stars, watchers, feedback, and, of course, pull requests! A PR need not necessarily be code, of course; it could be documentation, or something else. Whatever itch you care to scratch.

You can also sponsor the developers:

Comments
  • Create a config file to ignore some files, similar to .gitignore

    Create a config file to ignore some files, similar to .gitignore

    We copy the entire source module to $TMPDIR and then run from there. Create some way to ignore some paths in the source module so they're not copied, e.g. .git, LICENSE, README.md, Session.vim, .*.sw? (Vim swap files), etc.

  • Rewrite relative paths in go.mod replace directives

    Rewrite relative paths in go.mod replace directives

    We copy the source module to $TMPDIR and then run from there. This breaks replace directives in go.mod with relative paths.

    Rewrite them to be absolute paths.

  • Run each generated replacement function in Yaegi during the rewrite phase to flag problematic code early on

    Run each generated replacement function in Yaegi during the rewrite phase to flag problematic code early on

    Yaegi won't interpret some legal Go constructs (see #2 & #3). That can be frustrating to discover at runtime. Add the ability to run each generated replacement function during the rewrite phase, to detect such constructs early on. Probably shouldn't be fatal errors, since the user might not be interested in the function in question. Also, should be optional, since it'll probably take noticeable time, and also the user might know in advance (e.g. from previous runs) that it won't find anything anyway.

  • Handle tilde (~) in package paths

    Handle tilde (~) in package paths

    It seems that we choke on packages hosted on sourcehut because of the tilde character in their import path.

    Tilde failure
    08:01:27.295081 reloader.go:81: Running go list from /home/chris/Code/arbor/sprig
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/composer.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/icon-button.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/reply.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/text-form.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/theme.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/utils.go
    main.go:532: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/grl_register.go
    main.go:550: Failed generating symbol registration for git.sr.ht/~whereswaldon/forest-go/store: failed to format source: 32:50: illegal character U+007E '~' (and 20 more errors): 
    

    package theme

    import ( "git.sr.ht/~whereswaldon/forest-go" "git.sr.ht/~whereswaldon/forest-go/fields" "git.sr.ht/~whereswaldon/forest-go/store" "reflect" "github.com/got-reload/got-reload/pkg/reloader" _ "github.com/got-reload/got-reload/pkg/reloader/start" )

    func init() { reloader.RegisterAll(map[string]map[string]reflect.Value{ "git.sr.ht/~whereswaldon/forest-go/store": { // function, constant and variable definitions "NewArchive": reflect.ValueOf(store.NewArchive), "NewCacheStore": reflect.ValueOf(store.NewCacheStore), "NewMemoryStore": reflect.ValueOf(store.NewMemoryStore), "Walk": reflect.ValueOf(store.Walk), "WalkNodes": reflect.ValueOf(store.WalkNodes),

    	// type definitions
    	"Archive": reflect.ValueOf((*store.Archive)(nil)),
    	"CacheStore": reflect.ValueOf((*store.CacheStore)(nil)),
    	"ExtendedStore": reflect.ValueOf((*store.ExtendedStore)(nil)),
    	"MemoryStore": reflect.ValueOf((*store.MemoryStore)(nil)),
    	"Subscription": reflect.ValueOf((*store.Subscription)(nil)),
    	
    	// interface wrapper definitions
    	"_ExtendedStore": reflect.ValueOf((*_git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore)(nil)),
    	
    },
    })
    

    } // git_sr_ht~whereswaldon_forest_go_store_ExtendedStore is an interface wrapper for ExtendedStore type type git_sr_ht~whereswaldon_forest_go_store_ExtendedStore struct { WAdd func(a0 forest.Node) ( error) WAddAs func(a0 forest.Node, a1 store.Subscription) (err error) WAncestryOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WChildren func(a0 *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WCopyInto func(a0 forest.Store) ( error) WDescendantsOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WGet func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetCommunity func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetConversation func(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash) ( forest.Node, bool, error) WGetIdentity func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetReply func(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash, replyID *fields.QualifiedHash) ( forest.Node, bool, error) WLeavesOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WRecent func(nodeType fields.NodeType, quantity int) ( []forest.Node, error) WRemoveSubtree func(a0 *fields.QualifiedHash) ( error) WSubscribeToNewMessages func(handler func(n forest.Node)) ( store.Subscription) WUnsubscribeToNewMessages func(a0 store.Subscription) ()

    }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Add(a0 forest.Node) ( error) { return W.WAdd(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) AddAs(a0 forest.Node, a1 store.Subscription) (err error) { return W.WAddAs(a0, a1) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) AncestryOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WAncestryOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Children(a0 *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WChildren(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) CopyInto(a0 forest.Store) ( error) { return W.WCopyInto(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) DescendantsOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WDescendantsOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Get(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGet(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetCommunity(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetCommunity(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetConversation(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetConversation(communityID, conversationID) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetIdentity(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetIdentity(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetReply(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash, replyID *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetReply(communityID, conversationID, replyID) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) LeavesOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WLeavesOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Recent(nodeType fields.NodeType, quantity int) ( []forest.Node,  error) { return W.WRecent(nodeType, quantity) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) RemoveSubtree(a0 *fields.QualifiedHash) ( error) { return W.WRemoveSubtree(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) SubscribeToNewMessages(handler func(n forest.Node)) ( store.Subscription) { return W.WSubscribeToNewMessages(handler) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) UnsubscribeToNewMessages(a0 store.Subscription) () {  W.WUnsubscribeToNewMessages(a0) }
    

    The problem specifically appears to be:

    		// interface wrapper definitions
    		"_ExtendedStore": reflect.ValueOf((*_git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore)(nil)),
    
  • Handle packages with hyphens in their names

    Handle packages with hyphens in their names

    It's not terribly uncommon for a go package to have a hyphen in its final path component. I did this with some arbor libraries a while ago, and (while I regret it), this is done in many places. The convention is to import such libraries in an aliased way like so:

    import forestgo "git.sr.ht/~whereswaldon/forest-go"
    

    Unfortunately, we aren't currently handling this case in symbol registration, so any projects depending on a package with a hyphen in its name will fail to register symbols as below:

    hyphen failure
    08:06:30.479176 reloader.go:81: Running go list from /home/chris/Code/arbor/sprig
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/composer.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/icon-button.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/reply.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/text-form.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/theme.go
    main.go:524: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/utils.go
    main.go:532: Wrote /tmp/tmp.RGBSpyHWfM/widget/theme/grl_register.go
    main.go:550: Failed generating symbol registration for git.sr.ht/~whereswaldon/forest-go/store: failed to format source: 32:50: illegal character U+007E '~' (and 20 more errors): 
    

    package theme

    import ( "git.sr.ht/~whereswaldon/forest-go" "git.sr.ht/~whereswaldon/forest-go/fields" "git.sr.ht/~whereswaldon/forest-go/store" "reflect" "github.com/got-reload/got-reload/pkg/reloader" _ "github.com/got-reload/got-reload/pkg/reloader/start" )

    func init() { reloader.RegisterAll(map[string]map[string]reflect.Value{ "git.sr.ht/~whereswaldon/forest-go/store": { // function, constant and variable definitions "NewArchive": reflect.ValueOf(store.NewArchive), "NewCacheStore": reflect.ValueOf(store.NewCacheStore), "NewMemoryStore": reflect.ValueOf(store.NewMemoryStore), "Walk": reflect.ValueOf(store.Walk), "WalkNodes": reflect.ValueOf(store.WalkNodes),

    	// type definitions
    	"Archive": reflect.ValueOf((*store.Archive)(nil)),
    	"CacheStore": reflect.ValueOf((*store.CacheStore)(nil)),
    	"ExtendedStore": reflect.ValueOf((*store.ExtendedStore)(nil)),
    	"MemoryStore": reflect.ValueOf((*store.MemoryStore)(nil)),
    	"Subscription": reflect.ValueOf((*store.Subscription)(nil)),
    	
    	// interface wrapper definitions
    	"_ExtendedStore": reflect.ValueOf((*_git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore)(nil)),
    	
    },
    })
    

    } // git_sr_ht~whereswaldon_forest_go_store_ExtendedStore is an interface wrapper for ExtendedStore type type git_sr_ht~whereswaldon_forest_go_store_ExtendedStore struct { WAdd func(a0 forest.Node) ( error) WAddAs func(a0 forest.Node, a1 store.Subscription) (err error) WAncestryOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WChildren func(a0 *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WCopyInto func(a0 forest.Store) ( error) WDescendantsOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WGet func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetCommunity func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetConversation func(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash) ( forest.Node, bool, error) WGetIdentity func(a0 *fields.QualifiedHash) ( forest.Node, bool, error) WGetReply func(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash, replyID *fields.QualifiedHash) ( forest.Node, bool, error) WLeavesOf func(id *fields.QualifiedHash) ( []*fields.QualifiedHash, error) WRecent func(nodeType fields.NodeType, quantity int) ( []forest.Node, error) WRemoveSubtree func(a0 *fields.QualifiedHash) ( error) WSubscribeToNewMessages func(handler func(n forest.Node)) ( store.Subscription) WUnsubscribeToNewMessages func(a0 store.Subscription) ()

    }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Add(a0 forest.Node) ( error) { return W.WAdd(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) AddAs(a0 forest.Node, a1 store.Subscription) (err error) { return W.WAddAs(a0, a1) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) AncestryOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WAncestryOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Children(a0 *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WChildren(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) CopyInto(a0 forest.Store) ( error) { return W.WCopyInto(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) DescendantsOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WDescendantsOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Get(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGet(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetCommunity(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetCommunity(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetConversation(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetConversation(communityID, conversationID) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetIdentity(a0 *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetIdentity(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) GetReply(communityID *fields.QualifiedHash, conversationID *fields.QualifiedHash, replyID *fields.QualifiedHash) ( forest.Node,  bool,  error) { return W.WGetReply(communityID, conversationID, replyID) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) LeavesOf(id *fields.QualifiedHash) ( []*fields.QualifiedHash,  error) { return W.WLeavesOf(id) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) Recent(nodeType fields.NodeType, quantity int) ( []forest.Node,  error) { return W.WRecent(nodeType, quantity) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) RemoveSubtree(a0 *fields.QualifiedHash) ( error) { return W.WRemoveSubtree(a0) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) SubscribeToNewMessages(handler func(n forest.Node)) ( store.Subscription) { return W.WSubscribeToNewMessages(handler) }
    func (W _git_sr_ht_~whereswaldon_forest_go_store_ExtendedStore) UnsubscribeToNewMessages(a0 store.Subscription) () {  W.WUnsubscribeToNewMessages(a0) }
    

    I think the fix is to detect when the last component of the import path contains non-identifier characters. We can then remove them and automatically alias the name.

Tugas Alta Immersive Backend Golang Fundamental Programming (Pointer, Struct, Method, Interface)
Tugas Alta Immersive Backend Golang Fundamental Programming (Pointer, Struct, Method, Interface)

Tatacara Melakukan Setup Tugas clone project ini dengan cara git clone https://github.com/Immersive-Backend-Resource/Pointer-Struct-Method-Interface.g

Jan 9, 2022
keeper is package for Go that provides a mechanism for waiting a result of execution function until context cancel.

keeper is package for Go that provides a mechanism for waiting a result of execution function until context cancel.

Apr 18, 2022
A program to create assembly 8086 strings to print without using any printing/strings related function but only mov-xchg-int and loops

Assembly String builder tool A program to create assembly 8086 strings to print without using any printing/strings related function but only mov-xchg-

Feb 1, 2022
A helper function to create a pointer to a new object in Go 1.18+
A helper function to create a pointer to a new object in Go 1.18+

A helper function to create a pointer to a new object in Go 1.18+

Nov 9, 2022
A comprehensive, efficient, and reusable util function library of go.

Lancet Lancet is a comprehensive, efficient, and reusable util function library of go. Inspired by the java apache common package and lodash.js. Engli

Jan 8, 2023
Graceful - shutdown package when a service is turned off by software function

graceful Graceful shutdown package when a service is turned off by software func

Dec 29, 2022
Cpu-profiling - Basic example of CPU Profiling in Golang which shows the bottlenecks and how much time is spent per function

cpu-profiling Basic example of CPU Profiling in Golang which shows the bottlenec

Aug 2, 2022
libraries for various programming languages that make it easy to generate per-process trace files that can be loaded into chrome://tracing
libraries for various programming languages that make it easy to generate per-process trace files that can be loaded into chrome://tracing

chrometracing: chrome://tracing trace_event files The chrometracing directory contains libraries for various programming languages that make it easy t

Oct 6, 2022
Envoy utility to process envoy config for fast development and debugging.

envoyconf-tools Envoy is a proxy, really awesome and we are devs who often use it, face errors and struggle to debug it, when envoy config's source is

Oct 31, 2021
A process that receives probe information and stores it in a database for reporting and analysis

probed is a process that receives probe information and stores it in a database for reporting and analysis.

Nov 2, 2022
Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Flock is a project which provides a Go solution for system level file locks for all platforms Golang supports.

Feb 8, 2022
Clean-Swift source and test code auto-generator. It can save you time typing 500-600 lines of code.
Clean-Swift source and test code auto-generator. It can save you time typing 500-600 lines of code.

Clean-Swift source & test code auto generator Overview Run Output Basic Usage make config.yaml target_project_name: Miro // target project name copyri

Apr 13, 2022
Code generator that generates boilerplate code for a go http server

http-bootstrapper This is a code generator that uses go templates to generate a bootstrap code for a go http server. Usage Generate go http server cod

Nov 20, 2021
Sample app that prints the compute region it’s running on

This web application prints the Google Cloud datacenter it’s running on with information about where the datacenter is located (city, country and flag). It's used to demonstrate global load balancing capabilities of Google Cloud HTTPS Load Balancer, as it routes the request to the compute region closest to the visitor.

Jan 1, 2023
A package for running subprocesses in Go, similar to Python's subprocesses package.

A package for running subprocesses in Go, similar to Python's subprocesses package.

Jul 28, 2022
Cell is a Go package that creates new instances by string in running time.

Cell Cell is a Go package that creates new instances by string in running time. Getting Started Installing To start using CELL, install Go and run go

Dec 20, 2021
Tiny Go tool for running multiple functions concurrently and collecting their results into an error slice.

Overview Short for "ConCurrent". Tiny Go tool for running multiple functions concurrently and collecting their results into an error slice. Dependency

Nov 22, 2021
efaceconv - Code generation tool for high performance conversion from interface{} to immutable type without allocations.

efaceconv High performance conversion from interface{} to immutable types without additional allocations This is tool for go generate and common lib (

May 14, 2022
Type-driven code generation for Go

What’s this? gen is a code-generation tool for Go. It’s intended to offer generics-like functionality on your types. Out of the box, it offers offers

Jan 4, 2023