> ## Content Index
> Fetch the complete content index at: https://huizhou92.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# Go Rejected This Method for Five Years — Its Own Team Had Been Using It for Six
- URL: https://huizhou92.com/go-rejected-this-method-for-five-years-its-own-team-had-been-using-it-for-six/
- Published: 2026-08-20T12:07:35.000Z
- Updated: 2026-09-08T02:08:44.000Z
- Description: Go Rejected This Method for Five Years — Its Own Team Had Been Using It for Six. Golang Go Rejected This Method for Five Years — Its Own Team Had Been Usin。
- Author: huizhou92
- Tags: #Migrated-1788833207488, #Import 2026-09-08 02:07

#### Golang

### Go Rejected This Method for Five Years — Its Own Team Had Been Using It for Six

#### A method Russ Cox rejected in 2020 — but running inside Go’s own standard library since 2019

Go 1.27.0 shipped yesterday, August 19, 2026\. Buried in the release notes is one line most people will skim past: `net/url` gained `URL.Clone` and `Values.Clone`. I benchmarked it before writing anything else:

```
BenchmarkURL_ShallowCopy-10      18.4 ns/op   144 B/op   1 allocs/op 
BenchmarkURL_ParseString-10     190.2 ns/op   288 B/op   4 allocs/op 
BenchmarkURL_Clone-10             26.6 ns/op   192 B/op   2 allocs/op
```

(`go1.27rc2 darwin/arm64`, Apple M5, `-benchmem -count 5`, variance under 5%.)

`Clone` is **7x faster** than the round-trip most people reach for, `Parse(u.String())`. That part isn't surprising — a copy operation shouldn't have to go through an encoder and a parser.

What’s surprising is Go’s team sat on this for **five years** before shipping something this obvious.

#### **A proposal rejected on the spot**

In October 2020, someone proposed a `Clone` method for `URL` in [golang/go#41733](https://github.com/golang/go/issues/41733?ref=huizhou92.com). The reasoning was simple: `URL` carries a `*Userinfo` pointer field, and a plain `u2 := *u` shallow copy leaves nobody quite sure whether `Userinfo` ends up shared between the two.

Russ Cox pushed back at once:

> I’m confused about this. This operation would apply to many structs where you want to make a change to one field to produce a new copy. This is a common idiom in Go. Why is URL special? Why does it merit a special method? … I have not seen it come up often at all, which would warrant not adding a helper.

The proposer had no usage data, only his own coding habits as evidence. Less than two weeks later, he closed the issue himself and settled for something more modest: just document `u2 := *u` is safe ([#38351](https://github.com/golang/go/issues/38351?ref=huizhou92.com)).

A normal story would end there. Proposal rejected, everyone moves on.

#### **Six years of quiet internal use**

`net/http` has a file called `clone.go` that dates back to 2019, a year before the proposal existed. It holds an unexported function, `cloneURL`, used internally by `Request.Clone`, `Transport.Clone`, and a handful of other call sites:

```go
func cloneURL(u *url.URL) *url.URL { 
    if u == nil { 
        return nil 
    } 
    u2 := new(url.URL) 
    *u2 = *u 
    if u.User != nil { 
        u2.User = new(url.Userinfo) 
        *u2.User = *u.User 
    } 
    return u2 
}
```

Same logic as the `URL.Clone` that shipped five years later: shallow-copy the struct, deep-copy `User` on its own.

The Go team needed this more than anyone; they just never planned to expose it. The comment above the function is almost embarrassing to read:

```go
// cloneURL should be an internal detail, 
// but widely used packages access it using linkname. 
// Notable members of the hall of shame include: 
//   - github.com/searKing/golang 
// 
// Do not remove or change the type signature. 
// See go.dev/issue/67401.
```

Translation: this was supposed to stay internal, but third-party packages were already reaching in with `//go:linkname` to steal it — `github.com/searKing/golang` gets named on the "hall of shame" for doing just that. When the public API never comes, people find a way around it.

A method the maintainers used every day, that outsiders were already prying open with black magic, and the one thing missing was a front door. That’s the part of this release note actually worth stopping on.

![From internal cloneURL in 2019, to a rejected proposal in 2020, to data-backed evidence in 2025, to Go 1.27.0 shipping it in 2026 — the same code took six years.](https://huizhou92.com/content/images/2026/09/0-vb4llpmdgmf5emar.png)

From internal cloneURL in 2019, to a rejected proposal in 2020, to data-backed evidence in 2025, to Go 1.27.0 shipping it in 2026 — the same code took six years.

#### **Data flips the verdict**

In April 2025, Sean Liao reopened the proposal as [golang/go#73450](https://github.com/golang/go/issues/73450?ref=huizhou92.com). This time he skipped the argument and went straight to a GitHub code search:

- `url2 := *url1`\-style shallow copies: **21,200** hits
- `url2, _ = url.Parse(url1.String())`\-style round trips: **3,300** hits

One in eight users were paying for an encode-and-parse round trip just to copy a value, on top of handling an `error` return that should never fire. earthboundkid added the closer: `http.Header` already has `.Clone()`, and `url.Values` is the exact same underlying type — there was never a reason it shouldn't.

neild later laid out the sharper edge of the argument:

> There are no `*Userinfo` methods which mutate the value (and `Userinfo` is documented as immutable), but a user could modify a URL's `User` field with something like `*u2.User = *url.User("username")`. We generally recommend not worrying about this possibility (nobody should assign to a `*Userinfo` in this fashion) and recommend deep-copying a `URL` with a simple struct copy.

Nothing about `*Userinfo` can mutate it from the outside, and the docs say as much. But the language itself can't stop someone from writing `*u2.User = *url.User("username")` anyway. Deep-copying `Userinfo` on `Clone` is cheap insurance against a case nobody expects to hit.

On February 9, 2026, the proposal moved to likely accept. On February 18, it was formally **accepted**, with two CLs ([746800](https://go.dev/cl/746800?ref=huizhou92.com), [746801](https://go.dev/cl/746801?ref=huizhou92.com)) landing the same day. Six months later, Go 1.27.0 shipped and the method went public.

**“I think it’s common” didn’t move the review group. “I searched, and it’s 21,200 hits” did.** That’s arguably the more valuable outcome of this whole reversal — not `Clone` itself, but the evidence bar it took to get there.

#### **Inside the implementation: a new(expr) cameo**

The final `URL.Clone` reads like this:

```go
func (u *URL) Clone() *URL { 
    if u == nil { 
        return nil 
    } 
    uc := new(*u) 
    if u.User != nil { 
        uc.User = new(*u.User) 
    } 
    return uc 
}
```

`new(*u)` isn't a typo. It's `new(expr)`, the syntax Go 1.26 shipped — `new` used to accept only a type; now it can take an expression directly and return a pointer to a copy of it. The old way needed two lines:

```go
uc := new(URL) 
*uc = *u
```

Now it’s one. [I wrote about ](https://huizhou92.com/go-1-26-the-new-expr-feature-simpler-pointer-creation-clearer-code/)[new(expr)](https://huizhou92.com/go-1-26-the-new-expr-feature-simpler-pointer-creation-clearer-code/) after Go 1.26 shipped it. This is the first time I've watched it show up in the standard library doing actual work.

`Values.Clone` cuts an even bigger corner:

```go
func (vs Values) Clone() Values { 
    if vs == nil { 
        return nil 
    } 
    newVals := make(Values, len(vs)) 
    for k, v := range vs { 
        newVals[k] = slices.Clone(v) 
    } 
    return newVals 
}
```

`net/http`'s internal version doesn't even bother rewriting this much. It just type-converts and reuses `http.Header.Clone`:

```go
func cloneURLValues(v url.Values) url.Values { 
    return url.Values(Header(v).Clone()) 
}
```

`url.Values` and `http.Header` are both `map[string][]string` under the hood. Between identically-shaped types, one conversion is enough to borrow an implementation that already exists.

#### **Three old workarounds, ranked**

Back to the question that matters: before `Clone`, what did people do?

**Shallow copy**, `u2 := *u`. Technically safe: every field on `URL` except `User` is a value type. Nobody was ever quite sure, though. `Userinfo` being a pointer is enough to make people second-guess whether it's shared.

**Round trip**, `url.Parse(u.String())`. Safe by construction, at the cost of an encode and a parse, plus an `error` return that almost never fires.

**Encode round trip** (the `Values`\-only version), `url.ParseQuery(v.Encode())`. Same round-trip cost, and `Values` is a `map[string][]string` — a plain assignment only copies the map reference, leaving the underlying slices shared. That bug is quieter than the `URL` one, and easier to miss.

Benchmarked on the same machine:

```
BenchmarkValues_ManualDeepCopy-10    129.5 ns/op   512 B/op   6 allocs/op 
BenchmarkValues_EncodeParseQuery-10  475.0 ns/op   808 B/op  16 allocs/op 
BenchmarkValues_Clone-10             146.9 ns/op   512 B/op   6 allocs/op
```

![Same machine, same run: URL.Clone is 7x faster than Parse(String()), Values.Clone is 3.2x faster than Encode()+ParseQuery().](https://huizhou92.com/content/images/2026/09/0-aizbphqp_9gerkpa.png)

Same machine, same run: URL.Clone is 7x faster than Parse(String()), Values.Clone is 3.2x faster than Encode()+ParseQuery().

`Values.Clone` adds only 13% over a correctly-written manual deep copy, mostly the method call and nil check, and it's **3.2x** faster than the encode round trip.

`URL.Clone` is the better deal of the two. It costs a few nanoseconds more than the shallow copy nobody trusted, the price of one extra allocation for `Userinfo`, and in exchange you stop having to reason about whether the copy is safe.

#### **When Clone earns its keep**

Keep a base URL in config and clone it per request to change the `Path`. Write a middleware or SDK that hands out a `*url.URL` without wanting callers to mutate the copy you're still holding. Those are the cases where `Clone` is worth reaching for.

Mutating a `URL` field on a single goroutine was already safe. That's Go's general rule: if you own the value, you can change it, and `URL` was never a special case. That's the point Russ Cox pushed five years ago. What `Clone` fixes is unclear semantics and round-trip cost, not "can I mutate this?"

This reversal happened because someone was willing to spend an afternoon running a GitHub search, not because `Clone` is a particularly clever method.

“I think it’s common” doesn’t convince the Go team. “I searched, and here’s the number” does.