proposal: Go 2: string interpolation

Introduction
For ones who don't know what it is:
Swift
let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

Kotlin
var age = 21

println("My Age Is: $age")

C#
string name = "Mark";
var date = DateTime.Now;

Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now.");

Reasoning of string interpolation vs old school formatting
I used to think it was a gimmick but it is not in fact. It is actually a way to provide type safety for string formatting. I mean compiler can expand interpolated strings into expressions and perform all kind of type checking needed.
Examples
variable := "var"
res := "123\{variable}321" // res := "123" + variable + "321"

return errors.New("opening config file: \\{err}") // return errors.New("opening config file: " + err.Error())

var status fmt.Stringer
…
msg := "exit status: \{status}" // msg := "exit status: " + status.String()

v := 123
res := "value = \{v}" // res := "value = " + someIntToStringConversionFunc(v)

Syntax proposed

Using $ or {} would be more convenient in my opinion, but we can't use them for compatibility reasons
Using Swift \(…) notation would be compatible but these \() are a bit too stealthy

I guess  {…} and \(…) can be combined into \{…}
So, the interpolation of variable variable into some string may look like
"<prefix>\{variable}<suffix>"

Formatting also has formatting options. It may look like
"<prefix>\{variable[:<options>]}<suffix>"

Examples of options
v := 123.45
fmt.Println("value=\{v:04.3}") // value=0123.450

v := "value"
fmt.Println("value='\{v:a50}'") // value='<45 spaces>value'

etc
Conversions
There should be conversions and formatting support for built in types and for types implementing error and fmt.Stringer. Support for types implementing
type Formatter interface {
    Format(format string) string
}

can be introduced later to deal with interpolation options
Pros and cons over traditional formatting
Pros

Type safety
Performance (depends on the compiler)
Custom formatting options support for user defined types

Cons

Complication of a compiler
Less formatting methods supported (no %v (?), %T, etc)

Why can't we use just ${...}? Swift already has one syntax, JavaScript and Kotlin another, C# yet another, also Perl... Why invent one more variation? Wouldn't it be better to stick to the one most readable and already existing?

Why can't we use just ${...}? Swift already has one syntax, JavaScript and Kotlin another, C# yet another, also Perl... Why invent one more variation? Wouldn't it be better to stick to the one most readable and already existing?

Because we may have existing strings with ${…}. I do, for instance.
And \{ is not allowed right now.

This doesn't seem to have a big advantage over calling fmt.Sprintf.

This doesn't seem to have a big advantage over calling fmt.Sprintf.

No, it doesn’t besides full type safety, better performance and ease of use. Formatting done right for a language with static typing.

As far as I can tell this proposal isn't any more type safe than using fmt.Sprintf with %v.  It's essentially identical with regard to type safety.  I agree that a careful implementation could have better performance in many cases.  I'm agnostic on ease of use.

In order to implement this we would have to write, in the language spec, the exact formatting to use for all types.  We would have to decide and document how to format a slice, an array, a map.  An interface.  A channel.  This would be a significant addition to the language spec.

I think it's one of those questions where both ways have the right to exist, and it's just up to the decision makers to decide. In Go, the decision has been already made, quite a long time ago, and it works, and it's idiomatic. That's fmt.Sprintf with %v.
Historically, there are languages where interpolation inside a string has been present from the very beginning. Notably it's Perl. That's one of the reasons why Perl became so popular, because it was a super-convenient compared to sprintf() in C et al. And the %v wasn't invented then yet. And then there are languages where the interpolation was present, kind of, but was inconvenient syntactically, think of "text" + v1 + "text". JavaScript then introduced backtick-quoted literals, which are multi-line, and support interpolation of arbitrary expressions inside ${...}, which was huge improvement compared to "text" + v1 + "text". The Go too has backtick multi-line literals, but without ${...}. Who copied whom I don't know.
I don't agree with @ianlancetaylor that support of this will need substantial effort. In fact,  fmt.Sprintf with %v does exactly this, doesn't it? It looks for me like a different syntax wrapper to exactly the same thing under the hood. Am I right?
But I do agree with @ianlancetaylor that using  fmt.Sprintf with %v is as convenient. It's also exactly the same in length on the screen, and, what is IMHO very important - is already idiomatic for Go. It kind of makes Go Go. If we copy-implement every other feature from every other language, then it will no longer be Go, but every other language.
There's one more thing. From my long experience with Perl, where string interpolation was present from the very beginning, I can say that it's not that perfect. There's a problem with that. For simple and trivial programs, and probably for 90% of all programs, string interpolation of variables works just fine. But then, once in a while, you get a var=1.99999999999, and want to print it as 1.99, and you can't do that with standard string interpolation. You either need to do some conversion beforehand, or... Look into the docs, and re-learn long forgotten sprintf() syntax. Here is the problem - using string interpolation allows you to forget how to use sprintf()-like syntax, and probably it's very existence. And then when it's needed - you spend too much time and effort to do simplest things. I am talking in Perl context, and it was a decision of language designer(s) to do so.
But in Go, a different decision has been made.  The fmt.Sprintf with %v is already here, it's as convenient, as short as interpolated strings, and it's idiomatic, in that it's in the docs, in examples, everywhere. And it doesn't suffer from the problem of eventually forgetting how to print 1.99999999999 as 1.99.
Introducing proposed syntax will make Go a little more like Swift and/or more like JavaScript, and some may like that. But I think this particular syntax will not make it better, if not a little worse.
I think that existing way to print things should stay as it is. And if someone needs more - then there are templates for that.

If part of the argument here is safety at compile time, I don't agree that's a compelling argument; go vet, and by extension go test, have been flagging incorrect uses of fmt.Sprintf for a while.
It's also possible to optimize for performance today via go generate, if you really wish to do so. It's a tradeoff that's not worth it most of the time. I feel like the same applies to greatly expanding the spec; the tradeoff is generally not worth it.

Allowing function calls inside interpolated strings would be unfortunate - too easy to miss.
Not allowing them is another unnecessary special case.

If part of the argument here is safety at compile time, I don't agree that's a compelling argument; go vet, and by extension go test, have been flagging incorrect uses of fmt.Sprintf for a while. It's also possible to optimize for performance today via go generate, if you really wish to do so. It's a tradeoff that's not worth it most of the time. I feel like the same applies to greatly expanding the spec; the tradeoff is generally not worth it.

But type safeness should be guaranteed by the compiler, not by tooling. This is semantics; it is like saying that there should be a tool for verifying where you forgot to null check instead of having optionals or explicitly declared nullable values.
Apart from that - the only way for this to be safe is with dependent types. String interpolation is just more syntatic sugar for the same stuff as fmt.Sprintf and, although I'm all in favor of some good sugar, the whole go community seems not to be.

Or perhaps something like modifying the language to just work better with fmt.Printf & friends.
Like if fmt supported something like %(foo)v or '%(bar)q, then say that if a string literal containing %()` is used in a call to a variadic func/method, then all the referenced symbols are appended to the variadic list automatically.
e.g. this code:
name := "foo"
age := 123
fmt.Sprintf("The gopher %(name)v is %(age)2.1f days old.")

would really compile to:
name := "foo"
age := 123
fmt.Sprintf("The gopher %(name)v is %(age)2.1f days old.", name, age)

And fmt could just skip over the unnecessary (name) and (age) bits.
That's a pretty special case language change, though.

For me, type-checking as a reason to include string interpolation in the language is not that compelling. There is a more important reason, which is not depending on the order in which you write the variables in fmt.Printf.
Let's take one of the examples from the proposal description and write it in Go with and without string interpolation:

Without string interpolation (current Go)

name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())

Equivalent functionality with string interpolation (and mixing the @bradfitz comment, needed to express the formating options)

name := "Mark"
date := time.Now()

fmt.Printf("Hello, %{name}! Today is %{date.Weekday()}, it's %02{date.Hour()}:%02{date.Minute()} now.")

For me, the second version is easier to read and modify and less error-prone, as we do not depend on the order in which we write the variables.
When reading the first version, you need to re-read the line several times to check it is correct, moving your eyes back and forth to create a mental mapping between the position of a "%v" and the place where you need to put the variable.
I've been fixing bugs in several applications (not written in Go, but with the same issue) where the database queries had been written with a lot of '?' instead of named parameters (SELECT * FROM ? WHERE ? = ? AND ? != false ...) and further modifications (even inadvertently during a git merge) flipped the order of two variables 😩
So, for a language whose goal is to ease maintainability in the long term, I think this reason makes it worth it to consider having string interpolation
Regarding the complexity of this: I don't know the internals of Go compiler, so take my opinion with a grain of salt, but Couldn't it just translate the second version showed in the above example to the first one?

@ianlancetaylor pointed out that my sketch above (https://github.com/golang/go/issues/34174#issuecomment-532416737) isn't strictly backwards compatible, as there might be rare programs where this would change their behavior.
A backwards compatible variation would be to add a new type of "formatted string literal", prefixed by, say, an f:
e.g. this code:
name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.")

would really compile to:
name := "foo"
age := 123
fmt.Printf(f"The gopher %(name)v is %(age)2.1f days old.", name, age)

But then the double f (one in Printf followed by the f before the new type of string literal) would be stuttery.

String interpolation has the obvious benefit of higher readability (A core design decision of Go) and also scales better as the strings that one deals with become longer and more complicated (another core design decision of Go). Please notice also that using {age} gives the string context that it otherwise wouldn't have if you were only skim reading the string (and of course ignoring the type that was specified), the string could have ended "You are tall", "You are [at XXX location]", "You are working too hard" and unless you put in the mental energy to map the format method to each instance of interpolation it isn't immediately obvious what should go there. By removing this (admittedly small) mental hurdle the programmer can focus on the logic rather than the code.
I also don't understand the inner workings of the compiler, so I (perhaps foolishly) also assume that this could be implemented in the compiler, so that something like
fmt.printf("Hello %s{name}. You are %d{age}")
would compile to its equivalent current formulation.

The compiler implements the language spec.  The language spec currently says nothing at all about the fmt package.  It doesn't have to.  You can write large Go programs that don't use the fmt package at all.  Adding the fmt package documentation to the language spec would make it noticeably larger, which is another way of saying that it makes the language that much more complex.
That doesn't make this proposal impossible to adopt, but it is a large cost, and we need a large benefit to outweigh that cost.
Or we need a way to discuss string interpolation without involving the fmt package.  This is pretty clear for values of string type, or even []byte type, but much less clear for values of other types.

I'm not in favor of this proposal partly because of what @IanLanceTaylor said above and partly because, when you try to interpolate complex expressions with formatting options, any readability advantage tends to go out of the window.
Also it's sometimes forgotten that the ability to include variadic arguments in the fmt.Print (and Println) family of functions  already enables a form of interpolation. We can easily reproduce some of the examples quoted earlier with the following code which, to my mind, is just as readable:
multiplier := 3
message := fmt.Sprint(multiplier, " times 2.5 is ", float64(multiplier) * 2.5)

age := 21
fmt.Println("My age is:", age)

name := "Mark"
date := time.Now()
fmt.Print("Hello, ", name, "! Today is ", date.Weekday(), ", it's ", date.String()[11:16], " now.\n")

name = "foo"
days := 12.312
fmt.Print("The gopher ", name, " is ", fmt.Sprintf("%2.1f", days), " days old\n.")

Another reason to still add and have it in the language: https://github.com/golang/go/issues/34403#issuecomment-542560071

As noted above there is an existing way to approximately do this that even allows for formatting of the individual variables.  Therefore, this is a likely decline.  Leaving open for four weeks for final comments.

I'm regularly faced with building text blocks with well over 50 variables to insert. Over 70 in a few cases. This would be easy to maintain with Python's f-strings (similar to C# mentioned above).  The initial setup of fmt.Sprintf to manage these blocks is... ok. But god forbid I have to fix a mistake or modify the text in any way at all that involves moving or deleting %anything markers and their position related variables. And manually building maps to pass to template or setting up os.Expand is not a great option either. I'll take the speed (of setup) and ease of maintainability of f-strings over fmt.Sprintf any day of the week. And no, fmt.Sprint would not be hugely beneficial. Easier to setup than fmt.Sprintf in this case. But it loses much of it's meaning visually because your jumping in and out of strings. "My {age} is not {quality} in this discussion" doesn't jump in and out of strings the way that "My ", age, " is not ", quality, " in this discussion" does. Especially over the course of many tens of references. Moving text and references around is just copy and paste with f-strings. Deletions are just select and delete. Because your always within the string. This is not the case when using fmt.Sprint. It's very easy to accidentally (or necessarily) select non-string commas or double-quote string terminations and move them about breaking the formatting and requiring edits to massage it back into place. fmt.Sprint and fmt.Sprintf in these cases is far more time consuming and error prone than anything resembling f-strings.

That sounds like a pretty horrible task however you do it!
If I were faced with something like that, then I'd certainly be thinking in terms of text/template initially or, if it were too awkward to get my variables into a struct or map, I'd probably prefer fmt.Sprint to fmt.Sprintf but arrange the code in the following fashion:
s := fmt.Sprint(
    text1, var1,     // comment 1
    text2, var2,     // comment 2
    ....,
    text70, var70,   // comment 70
    text71,
)

Although that would take up a lot of screen real estate, it would be relatively easy to change, delete or move things about without too much risk of making a mistake.

There are some go string interpolation libraries, but without language features such as union types or generics, they are not as flexible nor fluid as in other languages:
package main

import (
	"fmt"

	"github.com/imkira/go-interpol"
)

func main() {
	m := map[string]string{
		"foo": "Hello",
		"bar": "World",
	}
	str, err := interpol.WithMap("{foo} {bar}!!!", m)
	if err != nil {
		fmt.Printf("Error: %v\n", err)
		return
	}
	fmt.Println(str)
}```

Once again, building a map just so I can use template/text, a 3rd party library or minimal system for os.Expand is great when you don't have 50 or more keys you need to create for variables that are already in existence for the rest of the code in question. And the only real argument against this feature is "you might forget how to properly use %s formatting" and might need to speed an extra 2 minutes using Google to lookup the formatting for the one instance you absolutely need the efficiency, specificity, or whatever of fmt.Sprintf? On the other hand you almost always need the efficiency, specificity, or whatever of fmt.Sprintf and never forget the formating at all because you always use it. I don't see the problem.

The other argument being it makes the language more complex? Yes, it does. Technically any addition to the language increases its complexity. And I'm all for not making the language more complex for trivial reasons. I REALLY would like to see the use of in for all the ways it's used in Python. But I'd settle for just replacing := range with in. I haven't hardly touched Python in the past year and only used it for two years prior. But nine times out of ten I type in first and then fix it with := range. But I'm not going to fight over that one. := range is sufficient and obvious to reason over code you've written prior. But f-strings and their kind is something completely different. That is hard core functionality, usability, and maintainability. It absolutely should be part of the Go spec. If there was any facility to do it as a 3rd party library I'd make the library and be done with it. But to my knowledge it's not even feasible without extra expansion to the Go language. So at the very least I think that expansion to the language should be in place.

@runeimp I want to make clear that your suggestion that we could start supporting "My {age} is not {quality} in this discussion" doesn't work.  That is a valid Go string today, and if we expected to interpolate age and quality that would break existing Go programs.  It would have to be something more like "My \{age} is not \{quality} in this discussion".  And the issues of formatting non-string values remain.

I appreciate that @ianlancetaylor. And just to be clear Python f-strings are f"My {age} is not {quality} in this discussion" or F'My {age} is not {quality} in this discussion' or any combination of any case f and a single or double quote. As mentioned, very much like how C# seems to do it. Also, while I'd prefer the Python f-string or C# style, I will absolutely accept ANYTHING that facilitates a similar usage. InterPolationACTIVATE"My @$@age###^&%$ is not @$@quality###^&%$ in this discussion"INTERpolationDEACTVATEandNullify would be acceptable. Lame, but acceptable. And I am not even joking. I'd take that lame excuse for string interpolation over fmt.Sprintf any day of the week. The maintenance benefits alone would justify it.

I understand that it would help your particular case.
In https://blog.golang.org/go2-here-we-come @griesemer wrote that any change to the language should

address an important issue for many people,
have minimal impact on everybody else, and
come with a clear and well-understood solution.

Is this an important issue for many people?

I have no idea but I'd expect it is. I think it's a feature that isn't important to anyone who's only ever had to deal with 5 reference or less on a short string. Or always had to manage it with templates anyway because of other spec requirements. But for those of us that do use it. It is quite sorely missed when not present. And for those who've never had the option (only every used C, C++, etc. prior to Go) it may be very hard to understand the benefits on a purely theoretical level as the examples in their experience are likely quickly forgotten or only remembered as the worst projects they've had to deal with trying to forget the pain points. I doubt you'd ever see a majority response regarding the issue unless most Go developers came from languages that actually support local variable string interpolation.
It's a problem I've always run into since I started as a professional web developer over 20 years ago. In most of those cases templating was common for front-end and back-end. At the tail end of that part of my career I eventually did work in Ruby on Rails and fell instantly in love with Ruby's "My #{age} is not #{quality} in this discussion" string interpolation. Even though the pound-curly-brace syntax struck me as very odd initially. When I transitioned to integration engineering I was mostly using Python 3 and was much happier using it's new str.format() system instead of it's old % formatted strings. With that one you would do something like "My {} is not {} in this discussion".format(age, quality). So using agnostic references at least type didn't matter for 90% of use cases. Which is just simpler to comprehend and only concern yourself with the index. Also named references like "My {age} is not {quality} in this discussion".format(age=my_age_var, quality=my_quality_var). Now if the same var is references 30 times you only have to specify it once in the parameters and it's easy to keep track, copy & paste, or delete. Named parameters (as input) is another feature of Python I miss in Go. But I can live without it if needs be. But fell in love again with f-strings (introduced in Python 3.6) the moment I laid eyes on it. String interpolation has always made my life easier.

@runeimp Could you post an example of one of these 50+ variable string interpolations (in any language you have them)? I think it might help the discussion to have an actual example of the code in question.

OK, this is not final code, is a simple example, has only 50 references, and the variable names have been changed to protect the innocent.
Go fmt.Sprintf
func (dt DataType) String() string {
	MMMCodeTail := " "
	if len(pn.MMMCode) > 0 {
		MMMCodeTail = "\n\t"
	}

	MMMVCTail := " "
	if len(pn.MMMVoipCommunication) > 0 {
		MMMVCTail = "\n\t"
	}

	MMMCCTail := " "
	if len(pn.MMMCombatConditions) > 0 {
		MMMCCTail = "\n\t"
	}

	MMMSRTail := " "
	if len(pn.MMMSecurityReporting) > 0 {
		MMMSRTail = "\n\t"
	}

	MMMLKTail := " "
	if len(pn.MMMLanguagesKnown) > 0 {
		MMMLKTail = "\n\t"
	}

	return fmt.Sprintf(`ThingID=%6d, ThingType=%q, PersonID=%d, PersonDisplayName=%q, PersonRoomNumber=%q,
	DateOfBirth=%s, Gender=%q, LastViewedBy=%q, LastViewDate=%s,
	SaleCodePrior=%q, SpecialCode=%q, Factory=%q,
	Giver=%s, Manager=%q, ServiceDate=%s, SessionStart=%s, SessionEnd=%s, SessionDuration=%d,
	HumanNature=%q, VRCatalog=%v, AdditionTime=%d, MeteorMagicMuscle=%v,
	VRQuest=%q, SelfCare=%v, BypassTutorial=%q, MultipleViewsSameday=%v,
	MMMCode=%q,%sMMMVoipCommunication=%q,%sMMMCombatConditions=%q,%sMMMSecurityReporting=%q,%sMMMLanguagesKnown=%q,%sMMMDescription=%q,
	SaleCodeLatest=%q, HonoraryCode=%q, LegalCode=%q, CharacterDebuffs=%q,
	MentalDebuffs=%q, PhysicalDebuffs=%q,
	CharacterChallenges=%q,
	CharacterChallengesOther=%q,
	CharacterStresses=%q,
	RelationshipGoals=%q, RelationshipGoalsOther=%q,
	RelationshipLobsters=%q,
	RelationshipLobstersOther=%q,
	RelationshipLobsterGunslingerDoublePlus=%q,
	RelationshipLobsterGunslingerPlus=%q,
	RelationshipLobsterGunslingerGains=%q,
	PersonAcceptsRecognition=%q,
	PersonAcceptsRecognitionGunslinger=%q,
	BenefitsFromChocolate=%v, DinnerForLovelyWaterfall=%v, ModDinners=%q, ModDinnersOther=%q,
	FlexibleHaystackList=%q, FlexibleHaystackOther=%q,
	ModDiscorseSummary=%q,
	MentallySignedBy=%q, Overlord=%q, PersonID=%d,
	FactoryID=%q, DeliveryDate=%s, ManagerID=%q, ThingReopened=%v`,
		dt.ThingID,
		dt.ThingType,
		dt.PersonID,
		dt.PersonDisplayName,
		// dt.PersonFirstName,
		// dt.PersonLastName,
		dt.PersonRoomNumber,
		dt.DateOfBirth,
		dt.Gender,
		dt.LastViewedBy,
		dt.LastViewDate,
		dt.SaleCodePrior,
		dt.SpecialCode,
		dt.Factory,
		dt.Giver,
		dt.Manager,
		dt.ServiceDate,
		dt.SessionStart,
		dt.SessionEnd,
		dt.SessionDuration,
		dt.HumanNature,
		dt.VRCatalog,
		dt.AdditionTime,
		dt.MeteorMagicMuscle,
		dt.VRQuest,
		dt.SelfCare,
		dt.BypassTutorial,
		dt.MultipleViewsSameday,
		dt.MMMCode, MMMCodeTail,
		dt.MMMVoipCommunication, MMMVCTail,
		dt.MMMCombatConditions, MMMCCTail,
		dt.MMMSecurityReporting, MMMSRTail,
		dt.MMMLanguagesKnown, MMMLKTail,
		dt.MMMDescription,
		dt.SaleCodeLatest,
		dt.HonoraryCode,
		dt.LegalCode,
		dt.CharacterDebuffs,
		dt.MentalDebuffs,
		dt.PhysicalDebuffs,
		dt.CharacterChallenges,
		dt.CharacterChallengesOther,
		dt.CharacterStresses,
		dt.RelationshipGoals,
		dt.RelationshipGoalsOther,
		dt.RelationshipLobsters,
		dt.RelationshipLobstersOther,
		dt.RelationshipLobsterGunslingerDoublePlus,
		dt.RelationshipLobsterGunslingerPlus,
		dt.RelationshipLobsterGunslingerGains,
		dt.PersonAcceptsRecognition,
		dt.PersonAcceptsRecognitionGunslinger,
		dt.BenefitsFromChocolate,
		dt.DinnerForLovelyWaterfall,
		dt.ModDinners,
		dt.ModDinnersOther,
		dt.FlexibleHaystackList,
		dt.FlexibleHaystackOther,
		dt.ModDiscorseSummary,
		dt.MentallySignedBy,
		dt.Overlord,
		dt.PersonID,
		dt.FactoryID,
		dt.DeliveryDate,
		dt.ManagerID,
		dt.ThingReopened,
	)
}

A Potential F-Strings Example
func (dt DataType) String() string {
	MMMCodeTail := " "
	if len(pn.MMMCode) > 0 {
		MMMCodeTail = "\n\t"
	}

	MMMVCTail := " "
	if len(pn.MMMVoipCommunication) > 0 {
		MMMVCTail = "\n\t"
	}

	MMMCCTail := " "
	if len(pn.MMMCombatConditions) > 0 {
		MMMCCTail = "\n\t"
	}

	MMMSRTail := " "
	if len(pn.MMMSecurityReporting) > 0 {
		MMMSRTail = "\n\t"
	}

	MMMLKTail := " "
	if len(pn.MMMLanguagesKnown) > 0 {
		MMMLKTail = "\n\t"
	}

	return fmt.Print(F`ThingID={dt.ThingID}, ThingType={dt.ThingType}, PersonID={dt.PersonID}, PersonDisplayName={dt.PersonDisplayName}, PersonRoomNumber={dt.PersonRoomNumber},
	DateOfBirth={dt.DateOfBirth}, Gender={dt.Gender}, LastViewedBy={dt.LastViewedBy}, LastViewDate={dt.LastViewDate},
	SaleCodePrior={dt.SaleCodePrior}, SpecialCode={dt.SpecialCode}, Factory={dt.Factory},
	Giver={dt.Giver}, Manager={dt.Manager}, ServiceDate={dt.ServiceDate}, SessionStart={dt.SessionStart}, SessionEnd={dt.SessionEnd}, SessionDuration={dt.SessionDuration},
	HumanNature={dt.HumanNature}, VRCatalog={dt.VRCatalog}, AdditionTime={dt.AdditionTime}, MeteorMagicMuscle={dt.MeteorMagicMuscle},
	VRQuest={dt.VRQuest}, SelfCare={dt.SelfCare}, BypassTutorial={dt.BypassTutorial}, MultipleViewsSameday={dt.MultipleViewsSameday},
	MMMCode={dt.MMMCode},{MMMCodeTail}MMMVoipCommunication={dt.MMMVoipCommunication},{MMMVCTail}MMMCombatConditions={dt.MMMCombatConditions},{MMMCCTail}MMMSecurityReporting={dt.MMMSecurityReporting},{MMMSRTail}MMMLanguagesKnown={dt.MMMLanguagesKnown},{MMMLKTail}MMMDescription={dt.MMMDescription},
	SaleCodeLatest={dt.SaleCodeLatest}, HonoraryCode={dt.HonoraryCode}, LegalCode={dt.LegalCode}, CharacterDebuffs={dt.CharacterDebuffs},
	MentalDebuffs={dt.MentalDebuffs}, PhysicalDebuffs={dt.PhysicalDebuffs},
	CharacterChallenges={dt.CharacterChallenges},
	CharacterChallengesOther={dt.CharacterChallengesOther},
	CharacterStresses={dt.CharacterStresses},
	RelationshipGoals={dt.RelationshipGoals}, RelationshipGoalsOther={dt.RelationshipGoalsOther},
	RelationshipLobsters={dt.RelationshipLobsters},
	RelationshipLobstersOther={dt.RelationshipLobstersOther},
	RelationshipLobsterGunslingerDoublePlus={dt.RelationshipLobsterGunslingerDoublePlus},
	RelationshipLobsterGunslingerPlus={dt.RelationshipLobsterGunslingerPlus},
	RelationshipLobsterGunslingerGains={dt.RelationshipLobsterGunslingerGains},
	PersonAcceptsRecognition={dt.PersonAcceptsRecognition},
	PersonAcceptsRecognitionGunslinger={dt.PersonAcceptsRecognitionGunslinger},
	BenefitsFromChocolate={dt.BenefitsFromChocolate}, DinnerForLovelyWaterfall={dt.DinnerForLovelyWaterfall}, ModDinners={dt.ModDinners}, ModDinnersOther={dt.ModDinnersOther},
	FlexibleHaystackList={dt.FlexibleHaystackList}, FlexibleHaystackOther={dt.FlexibleHaystackOther},
	ModDiscorseSummary={dt.ModDiscorseSummary},
	MentallySignedBy={dt.MentallySignedBy}, Overlord={dt.Overlord}, PersonID={dt.PersonID},
	FactoryID={dt.FactoryID}, DeliveryDate={dt.DeliveryDate}, ManagerID={dt.ManagerID}, ThingReopened={dt.ThingReopened}`,
	)
}

Which of the two would you like to maintain and add, update, delete every month for the next few years?

Can I choose neither? Both look horrifying to me.
What about your case can't be done with fmt.Sprintf("%#v", dt)? That is, what's the requirement on ordering? Exact formatting (e.g. = vs : for separators, %q vs. %v, ...)? Unprinted fields? Newlines?
Is some other program parsing the output, or is this for human consumption?
What about using reflect?
v := reflect.ValueOf(dt)
t := v.Type()
for i := 0; i < t.NumField(); i++ {
    f := t.Field(i)
    s += fmt.Sprintf("%s=%v", f.Name, v.Field(i))
    if t.Field(i).Type.Kind() == reflect.String && v.Field(i).Len() > 0 {
        s += "\n\t"
    } else {
        s += " "
    }
}

Both of those options are reasonable for this very specific example. But that's not going to fly for anything more complex. Or literally anything else. Like when the same reference is used many times in the one string. Or the values are not part of a single struct or map but are generated in the same scope. This is just a cludged example. Unfortunately I can't show any real code. But really just look at any web page with hundreds of words on it. Open the source view and imagine that 50+ words need to be dynamic. Now imagine this is not for a web page, you don't need a template system for any other reason but to possibly solve this problem, all the variables are already in scope being used in other parts of the code and/or in multiple places in this code block, and this text may be changing every 3 to 4 weeks in sometimes large and very significant ways. This may be part of a system that occassionally needs to generate any or all of PDF, CSV, SQL INSERT, email, an API call, etc.

Without real-world examples, it's really hard to tell whether this proposal is the right solution to the problem. Maybe string interpolation really is the solution, or maybe we're running into the XY problem. Maybe the right solution is already in the language somewhere, or the solution is a change to package reflect, or perhaps package text/template.

Firstly, I doubt whether this is an important issue for many people. This is the only serious discussion I can recall seeing about it and the emoji voting doesn't suggest overwhelming support. Third party libraries are not exactly plentiful either.
Secondly, it's worth pointing out that even fmt.Printf (and family) can do repeated arguments:
fmt.Printf("R%s T%[1]s T%[1]s was a canine movie star\n", "in")

Thirdly, I don't think it's necessarily the case that people who've used string interpolation in other languages would want to see it in Go. I've used it before in several languages and, whilst it's fine when you want to interpolate simple variables, as soon as you try to use more complex expressions, function calls, escaped flag characters etc. and then add formatting details to all of that (which inevitably means a 'printf' style approach) the whole thing can quickly become an unreadable mess.
Finally, it seems to me that, when you have a large number of variables to interpolate, then text/template is the best approach and perhaps therefore we should be looking at ways of making that more convenient to use when the variables are not part of a well-defined structure rather than integrating something of this complexity into the language itself.

Alternatively, building a DSL for the purpose:
func (dt *DataType) String() string {
    var s strings.Builder
    _ = fields.Format(&s,
        fields.PaddedInt("ThingID", 6, &dt.ThingID),
        fields.String("ThingType", &dt.ThingType),
        fields.String("PersonID", &dt.PersonID),
        fields.Time("DateOfBirth", &dt.DateOfBirth),
        ...
    )
    return s.String()
}

or
func (dt *DataType) String() string {
    var s fields.Builder
    s.PaddedInt("ThingID", 6, &dt.ThingID),
    s.String("ThingType", &dt.ThingType),
    s.String("PersonID", &dt.PersonID),
    s.Time("DateOfBirth", &dt.DateOfBirth),
    return s.String()
}

I can deeply understand @ianlancetaylor 's opinion. We know that we can live with fmt. Printf as we always have.
At the same time, I couldn't agree more with @alanfo 's opinion.

easier to read and modify and less error-prone

It is very important for the robust system programing.
I think adopting features/ideas from other languages isn't a shame thing at all when it can save a lot of precious time of people. Actually a lot of people are suffering from string interpolation problems.
There is the reason why other modern languages are adopting string interpolation.
I don't want Go remains just a little bit better language than C.
I think Go v2 is a chance to be much better Go.

IMHO, if the string interpolation feature is provided, most of us will choose to use it. We can feel it.
ps.
Unfortunately, @runeimp  's case is too edge case. I can feel the pain of the circumstance. But it blurs the proposal ideas. (No offense)

@doortts no offense taken. I always appreciate honest conversation.
I knew that posting real code could make a dent in the argument. And I also knew that posting an approximation that needs a little creativity to expand on mentally was probably going to just muddy the discussion. So I took a gamble because I don't expect anyone else to do it. But sharing real code is not an option and the hours it would take to put this in black and white with a concrete example, but otherwise to which I have zero use for, is just not something I have the energy for right now. I already well know this argument is uphill because of the reasons I already stated prior. I know I'm not one of a dozen people that see the real value in this feature. But most of those are almost certainly nowhere near this thread. I'm only here because I happened to see a post about it on Reddit. Otherwise I was almost completely unaware of this process. I've been pretty happy with how things have been moving forward with Go without feeling the need to jump in on the conversation. But I wanted to help with this discussion because I knew there just wouldn't be many voices fighting for it simply due to the amount of inertia to overcome whenever any change to the language is suggested. I don't think I've ever seen such strong opposition to changes in a language ever. And that makes it mighty intimidating to post in support of something new. I'm not saying any language should accept all new feature requests without review. Just that the reason it may seem quiet on the support side isn't always because the desire isn't there. And with that I ask that anyone interested in this feature please post something. Anything, so we can see some real numbers on this.

I think because it's the holidays, there is less attention in general. String interpolation is extremely useful and desired from a developer standpoint (which is why it is in so many other languages), but it seems this proposal is under cooked for such a large change at this point, which if rejected I don't think it means it isn't worth revisiting in the future. The small incremental changes that would make this obvious are not in place yet.
Dynamically resolving variables by name from the scope is not something that I think is possible (although yaegi Eval seems to do it?) presently.

I think the post @runeimp is referencing this one is my post in r/golang https://www.reddit.com/r/golang/comments/d1199a/why_is_there_no_equivalent_to_f_strings_in_python/
Where there is a bit more discussion.
I just wanted to throw my hat into the ring and say that variable interpolation would be massively helpful to me, coming from the python world something like fmt.sprintf makes Go seem seriously clunky and horrible to read/maintain. Go is mostly very elegant in how it makes doing the things that it values very easy and powerful. Readability and maintainability are some of the core tenants of its philosophy and it really shouldn't be this difficult to read a string and know what's going into it. There is a seriously non trivial amount of overhead that comes with understanding what will be printed whilst keeping in mind what variables are mapped to what items in a list at the end of that string. We simply shouldn't need to maintain that mental overhead.
If people feel that fmt.sprintf is appropriate for them it can be used. I don't feel that having both detracts from Go as a language since the proposal is unquestionably more legible than the current method, intuitive, and easy to maintain.
Is something like ```
fmt.sprintf("I am %{age} years old.")
or
fmt.sprintf("I am %T{age} years old")

I have the feeling that strings.Replacer (https://golang.org/pkg/strings/#Replacer) can almost do string interpolation, just missing the interpolation identifier (e.g. ${...}) and the pattern processing (e.g. if var i int = 2, ${i+1} should return "3")

Yet another approach would have a built-in function, say, format("I am a %(foo)s %(bar)d") that expands to fmt.Sprintf("I am a %s %d", foo, bar). At least, that's fully backwards compatible, FWIW.

From a language design perspective, it would be peculiar to have a builtin function expand to a reference to a function in the standard library.  To provide a clear definition for all implementations of the language, the language spec would have to fully define the behavior of fmt.Sprintf.  Which I think we want to avoid.

This probably won't make everyone happy but I think the below would be the most general. It's broken up into three parts

fmt.Printm functions that take a format string and a map[string]interface{}
accept #12854 so you can drop the map[string]interface{} when calling it
allow unkeyed names in map literals as shorthand for "name": name, or "qual.name": qual.name,

Taken together that would allow something like
fmt.Printm("i: {i}; j: {j}", {i, j})
// which is equivalent to
fmt.Printm("i: {i}; j: {j}", map[string]interface{}{
  "i": i,
  "j": j,
})

That still has the duplication between the format string and the arguments but it's a lot lighter on the page and it's a pattern that's easily automated: an editor or tool could automatically fill in the {i, j} based on the string and the compiler would let you know if they're not in scope.
That doesn't let you do computations within the format string which can be nice, but I've seen that overdone enough times to consider it a bonus.
Since it applies to map literals in general it can be used in other cases. I often name my variables after the key they'll be in the map I'm building.
A downside of this is that it can't apply to structs since those can be unkeyed. That could be rectified by requiring a : before the name like {:i, :j} and then you could do
Field2 := f()
return aStruct{
  Field1: 2,
  :Field2,
}

Do we need any language support for this? As go is now, it can look like this, either with a map type or with a fluid, more type-safe API:
package main

import (
	"fmt"
	"strings"
)

type V map[string]interface{}

func Printm(format string, args V) {
	for k, v := range args {
		format = strings.ReplaceAll(format, fmt.Sprintf("{%s}", k), fmt.Sprintf("%v", v))
	}
	fmt.Print(format)
}

type Buf struct {
	sb strings.Builder
}

func Fmt(msg string) *Buf {
	res := Buf{}
	res.sb.WriteString(msg)
	return &res
}

func (b *Buf) I(val int) *Buf {
	b.sb.WriteString(fmt.Sprintf("%v", val))
	return b
}

func (b *Buf) F(val float64) *Buf {
	b.sb.WriteString(fmt.Sprintf("%v", val))
	return b
}

func (b *Buf) S(val string) *Buf {
	b.sb.WriteString(fmt.Sprintf("%v", val))
	return b
}

func (b *Buf) Print() {
	fmt.Print(b.sb.String())
}

func main() {
	Printm("Hello {k} {i}\n", V{"k": 22.5, "i": "world"})
	Fmt("Hello ").F(22.5).S(" world").Print()
}

https://play.golang.org/p/v9mg5_Wf-qD
Ok, it's still inefficient, but it looks like it is not a lot of work at all to make a package that supports this. As a bonus, I included a different fluid API that might be said to simulate interpolations somewhat as well.

The "map string" proposal from @ianlancetaylor (although I personally prefer "value/variable string" with a v"..." syntax) also allows non-formatting use cases. For example, #27605 (operator overloading functions) largely exists because it is difficult today to make a readable API for math/big and other numeric libraries. This proposal would allow the function
func MakeInt(expression map[string]interface{}) Int {...}

Used as
a := 5
b := big.MakeInt(m"100000")
c := big.MakeInt(m"{a} * ({b}^2)")

Importantly, this helper function can coexist with the more performant and powerful API that currently exists.
This approach allows the library to perform whatever optimizations it wants for large expressions, and might also be a useful pattern for other DSLs, since it allows custom expression parsing while still representing values as Go variables. Notably, these use cases are not supported by Python's f-strings because the interpretation of the enclosed values is imposed by the language itself.

@HALtheWise Thanks, that is pretty neat.

I wanted to comment to show a little support for this proposal, from the stance of a general developer.  I've been coding with golang for over 3 years professionally.  When I moved to golang (from obj-c/swift) I was disappointed that string interpolation was not included.  I've used C and C++ for over a decade in the past, so printf wan't a particular adjustment, other than feeling like going backwards a little -- I've found that it does indeed make a difference with code maintenance and readability for more complex strings.  I've recently done a little bit of kotlin (for a gradle build system), and using string interpolation was a breath of fresh air.
I think string interpolation can make string composition more approachable for those new to the language.  It's also a win for technical UX and maintenance, due to the reduction of cognitive load both when reading and writing code.
I am glad that this proposal is getting real consideration.  I look forward to the resolution of the proposal.  =)

If I understand correctly, the @ianlancetaylor proposal is:
i := 3
foo := m"twice i is %20{i * 2}s :)"
// the compiler will expand to:
foo := map[string]interface{}{
	"": "twice i is %20{i * 2}s :)",
	"i * 2": 6,
}

After that, a print function will handle that map, parse entire template again, and take few advantages of the pre-parsed template
But if we expand m"str" to a function?
i := 3
foo := m"twice i is %20{i * 2}s :)"
// the compiler will expands to:
foo := m(
	[]string{"twice i is ", " :)"}, // split string
	[]string{"%20s"},               // formatter for each value
	[]interface{}{6},               // values
)

This function has the following signature:
func m(strings []string, formatters []string, values []interface{}) string {}

This function will perform better because, to take more advantage of the pre-parsed template, and much more optimizations could be done similar as Rust does with the println! function.
What I'm trying to describe here is very similar to the Tagged functions of the Javascript, and we could discuss whether the compiler should accept user functions to format string ex:
foo.GQL"query { users{ %{expectedFields} } }"

bla.SQL`SELECT *
	FROM ...
	WHERE FOO=%{valueToSanitize}`

@rodcorsi If I'm reading your suggestion correctly, it requires building fmt.Printf formatting into the language proper, because the compiler will have to understand where %20s starts and ends.  That is one of the things I was trying to avoid.
Also note that my suggestion is not at all tied to fmt.Printf formatting, and can be used for other kinds of interpolation as well.

I would be opposed to treating m"..." as expanding to a function call, because it obscures what's actually going on, and adds what's effectively a second syntax for function calls. It does generally seem reasonable to pass a more structured representation than a map, to avoid needing matching reimplementations of the parsing behavior everywhere. Perhaps a simple struct  with a slice of constant string sections, a slice of strings for things in braces, and an interface slice?
m"Hello {name}" ->
struct{...}{
    []string{"Hello ", ""},
    []string{"name"},
    []interface{}{"Gopher"}

The second and third slices must be the same length, and the first must be one longer. There are other ways to represent this as well to encode that constraint structurally.
The advantage this has over a format that directly exposes the original string is that there's a looser requirement to have a correct and performant parser in the function that consumes it. If there's no support for escaped characters or nested m-strings, it probably isn't a big deal, but I'd rather not need to reimplement and test that parser, and caching it's result could cause runtime memory leaks.

If "formatting options" is a frequent desire of things using this syntax, I could see there being a place for them in the spec, but I'd personally go with a syntax like m"{name} is {age:%.2f} years old" where the compiler just passes everything after the : on to the function.

Hello I wanted to comment on this to add support to this proposal. I been working with many different languages in that past 5 years (Kotlin, Scala, Java, Javascript, Python, Bash and some C) and I'm learning Go now.
I think string interpolation is a must have in any modern programming language, the same way as type inference is.
For those arguing that you can accomplish the same thing with Sprintf, then, I don't understand why we have type inference in Go, you could accomplish the same thing writing the type right? Well, yes, but the point here is that string interpolation reduce a lot of the verbosity you need to accomplish that and is more easy to read (with Sprintf you have to jump to the argument list and the string back and forth to make sense of the string).
In real life software, this is much appreciate feature.
It is against the Go minimalist design? No, it's not a feature that allow you to do crazy things or abstractions that complicate your code (like inheritance), is just a way of writing less and add clarity when you read the code, which I think is what Go is trying to do (remember type inference)

Formatting done right for a language with static typing

Haskell has libraries and language extensions for string interpolation. It's not a type thing.

Coming from other languages (TypeScript/JavaScript, Java, Python, PHP, some of others) and forced to use go (because of interest in kubernetes autoscaler) I don't understand the claim "easy". It's not helpful to have a bunch of (sometimes complex and hard to understand) workarounds because of missing language features. And I don't mean my code as a beginner, but code from professional go developers. More precise and honest claim for "easy" (or in some other projects: "lightweight") is "limited".
Thank to @alanfo I found a workaround to have at least some readability for multiline strings:
func interpolate(template string, variables map[string]string) string {
	f := func(ph string) string {
		return variables[ph]
	}
	return os.Expand(template, f)
}

func main() {
	fmt.Println(interpolate(
		`The gopher ${name}
is ${days} days old.`,
		map[string]string{
			"name": "blub",
			"days": fmt.Sprintf("%2.1f", 12.312),
		},
	))
}

The similar (but TBH not the same floating point format - I'm too lazy - exercise for the reader) thing in TypeScript:
const name = 'blub'
const days = 12.312
console.log(`The gopher ${name}
is ${days.toFixed(1)} days old.`)

In fact, with library like outdent you get a very nice indentation of code and don't need that the lines after first starts at beginning of the line to not to have white spaces at beginning:
const indent = outdent({trimTrailingNewline: false})

function example() {
  const name = 'blub'
  const days = 12.312.toFixed(1)
  const template = indent`
    The gopher ${name}
    is ${days} days old.
  `
  console.log(template)
}

Coming from other languages (TypeScript/JavaScript, Java, Python, PHP, some of others) and forced to use go (because of interest in kubernetes autoscaler) I don't understand the claim "easy". It's not helpful to have a bunch of (sometimes complex and hard to understand) workarounds because of missing language features. And I don't mean my code as a beginner, but code from professional go developers. More precise and honest claim for "easy" (or in some other projects: "lightweight") is "limited".
Thank to @alanfo I found a workaround to have at least some readability for multiline strings:
func interpolate(template string, variables map[string]string) string {
	f := func(ph string) string {
		return variables[ph]
	}
	return os.Expand(template, f)
}

func main() {
	fmt.Println(interpolate(
		`The gopher ${name}
is ${days} days old.`,
		map[string]string{
			"name": "blub",
			"days": fmt.Sprintf("%2.1f", 12.312),
		},
	))
}

The similar (but TBH not the same floating point format - I'm too lazy - exercise for the reader) thing in TypeScript:
const name = 'blub'
const days = 12.312
console.log(`The gopher ${name}
is ${days.toFixed(1)} days old.`)

In fact, with library like outdent you get a very nice indentation of code and don't need that the lines after first starts at beginning of the line to not to have white spaces at beginning:
const indent = outdent({trimTrailingNewline: false})

function example() {
  const name = 'blub'
  const days = 12.312.toFixed(1)
  const template = indent`
    The gopher ${name}
    is ${days} days old.
  `
  console.log(template)
}

Grr I has always missed this in go.  I've found a reddit topic dated one year ago and thought cool, these guys devs must've added something to the language already. Sigh..

Just a little experience report: I am currently building a User Interface library and needed string interpolation as a facility for the clients of the library.
Goal was to have the string variables storable in a map[string]string, modifying the map values would recompile the string. Didn't have time to implement it so I took a shortcut using fmt.Sprintf although it's really brittle, requiring to pass the string variable names separately.
Thought of using text/template but it looked a bit complex and I wouldn't be able to use it anyway for compilation with tinyGo as it is not supported.
So, just to say that I too, wish for some kind of string interpolation.

@atdiar may github.com/sirkon/go-format may be helpful then. In this form — format.FormatX shortcuts are for "static" configurations and are not a good match for use input.

Haskell has libraries and language extensions for string interpolation. It's not a type thing.

It is :)
You see, Haskell type system (and syntax) allows to implement formatting at library level. Not possible with Go. So it IS a type thing :)

@atdiar my https://github.com/sirkon/go-format may be helpful then. In this form — format.FormatX shortcuts are for "static" configurations and are not a good match for user input.
PS it doesn't look like built-in formatting would be helpful in your case.

Looks a bit too complex.
Basically,  given a templated string:
" Hi, my name is $(firstname) $((lastname) !"
I want to use it to configure a map type.

type Interpolater struct{
    Template string
    Params map[string]string
}

func(i Interpolater) SetTemplate(s string) error {...}

// String returns the string result
func(i Interpolater) String() string{...}

func(i Interpolater) Set(param string, value string)

I could naively write the string template parsing function by iterating through the string characters and substracting the param names. But people seem to use full-fledged parser/lexers for such tasks so I left it for later.

PPS Created the ticket just for fun. Didn't expect then it would gain so many reactions LOL.

The fact this "simple" feature got into such a big debate just proves how important it is for development usability and ergonomics.
For devs used to C and C-like languages, it's acceptable to simply use fmt.Sprintf, even if it leads to more code and more brain consumption for a side task like string interpolation.
For those who don't use C-like languages or came from other backgrounds, the lack of a proper string interpolation syntax (something considered "basic" for any modern language) is odd, at least.
I understand the argument that Go must keep a simple and lean language, but the question here is: How much do we want to tradeoff between simpleness and usability?
I mean, if we really care only about simpleness, should Go even bother to have arithmetic operators in the first place? Why not simply add needed functions to math package and use it instead? Then everyone could create wrapper functions themselves to help with those operations if they use them a lot, this should not be concern of the language syntax... See how it makes no sense? That's how everyone else feels about lack of string interpolation in a 2000s language.

As a library, that would give something akin to this I guess:  https://play.golang.org/p/RGhnrwsm5a2
package main

import (
	"fmt"
	"strings"
)

func Parse(input string, tokenstart string, tokenend string) []string {
	result := make([]string, 0)
	ns := input

	startcursor := strings.Index(ns, tokenstart)
	if startcursor == -1 {
		return result
	}
	ns = ns[startcursor:]
	ns = strings.TrimPrefix(ns, tokenstart)

	endcursor := strings.Index(ns, tokenend)
	if endcursor < 1 {
		return result
	}
	tail := ns[endcursor:]
	result = append(result, strings.TrimSuffix(ns, tail))

	subresult := Parse(strings.TrimPrefix(tail, tokenend), tokenstart, tokenend)
	result = append(result, subresult...)
	return result

}

type TemplatedString struct {
	Template   string
	delimstart string
	delimend   string
	Parameters map[string]string
}

func NewTemplateString(t string) TemplatedString {
	params := make(map[string]string)
	delimstart := `${`
	delimend := `}`
	paramnames := Parse(t, delimstart, delimend)
	for _, name := range paramnames {
		params[name] = "[undefined]"
	}
	return TemplatedString{t, delimstart, delimend, params} // By default, the template parameters are stored between `${` and `}`
}

func (t TemplatedString) SetParamDelimiters(start string, end string) TemplatedString {
	t.delimstart = start
	t.delimend = end
	return t
}

func (t TemplatedString) SetParameter(name string, value string) {
	_, ok := t.Parameters[name]
	if ok {
		t.Parameters[name] = value
	}
}

func (t TemplatedString) String() string {
	res := t.Template
	for k, v := range t.Parameters {
		p := t.delimstart + k + t.delimend
		res = strings.ReplaceAll(res, p, v)
	}
	return res
}

func main() {

	t := NewTemplateString("My ${name} is the best. I love him ${number} !")
	t.SetParameter("name", "pet")
	t.SetParameter("number", "3000")
	fmt.Print(t.String())

	t = NewTemplateString("Your ${name} is the best too. I love him ${number} !")
	t.SetParameter("name", "pet")

	fmt.Print(t.String())
}

Golang should have variable interpolation for anything that supports .String(). If the argument is "there is no interpolation syntax that won't break backwards compatibility with people's strings" that's arguing an edge case is worth preserving against a primary use case that would improve every go programmer's job on a daily (hourly? constant?) basis, which is absolutely insane. This kind of stuff is what drives off new and entry level programmers to Go and continues to hamper the growth of the language.

Which satisfies Stringer or is a string. Another example where typelists as discriminated unions could be useful.
I have actually run into this issue several times but it's another topic.

I want to show my support on this proposal, and I want to give an example to illustrate comparing to `${variable}` or m"{variable}", the %s/%d/... way to generate strings is more prone to error on the human cognitive level.
It's not obvious in trivial case, but when the use case scales, the disadvantage of prone to error surfaces. Below is just one example, the idea should apply to all similar examples where the number of variables to interpolate grows. It need not be as many as 50 variables like what @runeimp mentioned.
fmt.Sprintf(
  `
    SELECT * FROM table_name
    WHERE
      title=%s AND
      (name=%s AND date=%s AND ratings=%d) OR
      priority=%d AND
      group=%s AND
      date=%s OR
      (genre=%s AND priority=%d AND  ratings=%d)
  `,
    title,
    name, date, ratings,
    priority,
    group,
    date,
    genre, priority, ratings
)

Writing the initial version might not be much of a problem, for example you could just do a checksum - 6 lines of %s/%d, 6 lines of variable arguments. You can even do a little code formatting so name, date, ratings are on the same line to "sync" with the shape of the string containing %s/%d, although this is not always possible.
The maintainability problem surfaces when you try to edit it, e.g. change orderings, add some new text with new variables, remove text with some variables.
There're extra check needed to make sure the ordering of the variable args are right, the number of the %s, %d matches exactly the number of arguments.
Some suggest why not just use Printfln, so that variables have proximity to the string context? You can actually try it yourself - literally type and convert the fmt.Sprintf version above to fmt.Sprintln version below.
fmt.Sprintln(
    "SELECT * FROM table_name ",
    "WHERE ",
      "title=", title, "AND ",
      "(name=", name, " AND date=", date, " AND ratings=, ratings, ") OR ",
      "priority=", priority, " AND ",
      "group=", group, " AND ",
      "date=", date, " OR",
      "(genre=", genre, " AND priority=", priority, " AND  ratings=", rating, ")"
)

Those extra spaces you need to be careful to insert, lots of commas and double quotes, not to say when you need to edit later on, you need to tip toe on the commas, quotes and space constantly, alongside the app logic.
Imagine with string interpolation like `title=${title}`, I can focus more on the app logics, not the ordering of the variables arguments of fmt.Sprintf and the ordering of "%s/%d", not whether the number of arguments matches the number of %s, %d, ... or not:
// pseudo code
fmt.Printf(
  f`
    SELECT * FROM table_name
    WHERE
      title=${title} AND
      (name=${name} AND date=${date} AND ratings=${ratings}) OR
      priority=${priority} AND
      group=${group} AND
      date=${date} OR
      (genre=${genre} AND priority=${priority} AND ratings=${ratings})
  `
)

I've already bumped into errors several times because of a mismatch of the %s/%d.. with the following variable arguments as the code changes over time. I do think comparing to string interpolation, fmt.Sprintf() is more prone to error. It has been the only way for C, but for a modern language like Go, it should be also designed for less human error - in this case - developers. This is quite of an usability problem, and developer experience matters a lot. Checking ordering and matching while jumping back and forth are needed for experienced users as well, and would be a barrier for novel users.

I think the explicit argument index feature of package fmt is often forgotten. It doesn't achieve the same degree of maintainability as interpolated strings, but it does reduce the burden in cases like these.

This has been discussed quite a bit in this issue that has sadly been closed.  I haven't taken the time (yet?) to re-write the proposal out of concern that I don't have the time to champion it right now.  There is precedent for multi-line string support in other languages (my thoughts) and I don't know that we need to re-invent the wheel there.
I think fixing multi-line strings like the SQL query above is a separate change, though related to string interpolation.  IMO ideally you could copy/paste a SQL query like in your example into a multi-line string and have it keep it's formatting, rather than having it be translated into an interpolated (and re-formatted) string.

The idea of string literals that evaluate to map[string]interface{} is interesting, especially given techniques like @HALtheWise's https://github.com/golang/go/issues/34174#issuecomment-620890833, but I worry about its performance. Creating a map is not free, and it would force every value used in it (as well as strings for the keys) to be allocated on the heap. Of course performance isn't everything, but it would be another case where performance-sensitive paths have to give up on the nice feature and write code that is comparatively ugly. I often write those performance-sensitive paths; I'm already used to feeling this sensation.
So, having re-encountered this proposal after a while, I would like to propose an alternative.
It seems most of @ianlancetaylor's arguments against adding string interpolation as originally proposed is about formatting non-string types. In particular, in https://github.com/golang/go/issues/34174#issuecomment-540828621, he mentions, "Or we need a way to discuss string interpolation without involving the fmt package. This is pretty clear for values of string type, or even []byte type, but much less clear for values of other types." The discussion since has mostly regarded approaches that can handle those other types, but I would find it satisfactory to obviate the need by allowing only expressions that evaluate to values of type string or fmt.Stringer in formatting directives.
More formally, consider the following change to the spec. (I'd like to stress that I am proposing semantics, not syntax per se; f"{x}" could be "\{x}" or $"{x}" or anything else, with a different construction.) Under the section "expressions," add a subsection along these lines:

Interpolated string literals
Interpolated string literals represent string values constructed from the concatenation of constant character sequences interspersed with dynamically evaluated interpolation sequences. They are written as interpreted string literals prefixed with f, with interpolation sequences surrounded by braces. Outside of interpolation sequences, it is an error for an interpolated string literal to contain different counts of "{" and "}" characters or a "}" character before a "{" character.
InterpolatedLit = `f"` { unicode_char | escaped_char } `"` .
InterpolationSequence   = "{" Expression "}" .

Each interpolation sequence specifies an expression to evaluate at run-time. The expression must have a result type of string, or the result type must have a method String() string, or it must be a constant representable as type string.
Interpolated string literals first have each interpolation sequence extracted. The remaining portion of the literal is interpreted as if it were an interpreted string literal. Then, the expression within each interpolation sequence is evaluated. If the result is a value assignable to type string or a constant representable as type string, then it replaces the interpolation sequence. Otherwise, the result of calling its String method replaces the interpolation sequence.
[Examples ensue.]

In addition to that section, add to the section on assignability:

A value x is assignable to a variable of type T ("x is assignable to T") if one of the following conditions applies:

...
x is an (evaluated) interpolated string literal and T is or has underlying type string.

In the section on constant expressions, add:

An interpolated string literal is a constant if each interpolation sequence in the literal contains a constant expression.

And in the section on order of evaluation, add the interpolation sequences of interpolated string literals to the list of places where expressions are evaluated in lexical left-to-right order.
I think the main disadvantage to this approach is that numbers can't be formatted, and that seems like a desirable feature. However, in terms of the spec, it would be just a few sentences to add formatting for integers, or it would be straightforward to do s := strconv.Itoa or s := fmt.Sprint to do f"{s(len(f))}".

@slycrel
I don't see any discussion related to interpolated strings in that issue. I think this and more flexible raw string literals would solve different problems, although they might occur in similar places.

@slycrel
I don't see any discussion related to interpolated strings in that issue. I think this and more flexible raw string literals would solve different problems, although they might occur in similar places.

I don't mean to be off-topic here.  I was simply mentioning that there has been a lot of discussion around maintaining copy/pasting multi-line strings in that other issue, in particular using SQL queries as examples like @rivernews mentions.  And that other languages have solved that somewhat independently of string interpolation.  I definitely care more about string interpolation overall and think it would have a greater impact on a day to day basis as far as language usage and readability.
The point I was trying to make...  String interpolation can be solved while saying large blocks of inline strings is maybe another (related) issue that could/should be addressed separately.  IMO multi-line strings should be solved first because the next logical improvement to multi-line strings is how string interpolation works with them.  Which is what led me to that issue in the first place while thinking about string interpolation in general.
I guess I see 3 things here.  String interpolation in general, maintaining copy/pasted multi-line strings, and string interpolation in multi-line strings.  I think they all have some overlap.  And... these could be incrementally added separately rather than all dealt with together.  Though I am not opposed to them all being fixed at once.

In case someone is interested, I just wrapped up a small string interpolation library:
https://src.eruta.nl/beoran/ginterpol
Note that string interpolation is simple, but not that simple. If the go compiler were to support string interpolation if would have to support string formatting at runtime directly, causing bloat.

I wanted to add support for this proposal as well. JavaScript's Tagged Template Literals was mentioned earlier and I felt the idea deserved to be elaborated on a little bit. I think by adding tag functions you could avoid adding fmt formatting language to the spec which would be cumbersome to include in my opinion. Tag functions could then be used to expand the functionality of literals. Say a tag function must match this type.
// Tag function definition.
type tag func (template []string, parts ...interface{}) string

The tag syntax would split the string on interpolations, providing them as an array, and all interpolated values in an array.
Then implementing a tag function wrapper of Sprintf could look like this.
func Sprintf(template []string, parts ...interface{}) string {
	fullTemplate := strings.Join(template, "")
	return fmt.Sprintf(fullTemplate, parts)
}

// Usage:
Value := 20
Formatted := Sprintf"Value is %d{Value}, %T{Value}"
// Long form Usage:
LongFormatted := Sprintf([]string{"Value is %d", ", %T", ""}, Value, Value)

The tag syntax would basically be sugar on top of the normal long form usage.
There is still an issue with basic interpolation though. Either the spec needs basic formatting rules to allow simple interpolation without a tag function like Plain := "Value is {Value}" or interpolation should only be allowed if you use a tag function. Personally I think the second option would be best even though it is more restrictive. This would keep formatting rules out of the spec and as an implementation detail of tag functions.
The downside of this approach is that this tag syntax can seem magical and doesn't look like a function call even though it is one. This might also be out of scope for a string interpolation proposal and would be better as a complimentary proposal. Personally though I think this could strike a good enough balance between simplicity and flexibility to be worth investigating.
As a note JavaScript has seen tag functions used for DSL's as well to help improve correctness which would be great to see brought to go. Getting intelisense for sql"SELECT * FROM {Table}" would greatly improve DX.

Perhaps templates provide the functionality you are looking for?

For internal use (not sharable) I created something much like @jimmyfrasche's suggestion one evening:

fmt.Printm functions that take a format string and a map[string]interface{}

I made it support normal sprintf modifiers with the interpolated values, so we could do type specific formatting. It took an object or a map (much like text/template does). We didn't have to wait for it to end up in stdlib; I put it to use right away on a few projects, and shared within our internal go community.   For a couple of my projects, it helped quite a bit.
The more interesting takeaway, I think, is that it has near zero adoption.  I got a lot of people saying it looked cool; but at the end of the day people still use sprintf and text/template.
If anything, I'd say making text/template more ergonomic would be a better investment.  Perhaps a template.Print(format, object) that hides the template parsing and execution bits.  But that would be different proposal.

I don't know if this will add anything meaningful to the conversation or not, but this is my Golang journey, coming from a background of multiple programming languages (Swift, TypeScript, PHP, Python, C).

At first, I thought it was odd we didn't use string interpolation in tutorials. I figured we would get back to it later.

Then I struggled with the idea of not having classes, but I eventually overcame this roadblock and embraced structs and receivers.

Then I struggled with the community's frequent use of single character variables, but overcame this with my own best practices standard.

Finally, I had to do some more string operations and began really looking into string interpolation. And now I am here. I really like how Golang is simple and opinionated. I like that there is usually only one way to do things. In my experience, Golang would do better to trade fmt.Sprintf() in for string interpolation. If there is only going to be one way to format data in strings, I would rather it be string interpolation.

@jfesler I think the adoption issue is the same as the problem for all solutions in Go as it doesn't support automatic access of local variables. If I have to create a data structure of any sort to collect all the variables already in flight to utilize string interpolation it is not string interpolation as utilized in all other languages such as Python, Ruby, JavaScript, etc. I might as well use text/template, html/template, os.Expand, or whatever as I've already lost most of what makes using string interpolation so massively time saving. If I've just spend a few hour writing code with lots of variables in scope I want to just write in interpolated string and go. Not spend an additional, 30 minutes or more writing up a data structure and implementation code for a template system to access tens (possibly hundreds?) of variables.

I once saw a great talk by Rob Pike, where he noted the process that languages were borrowing features from one another, and how this process meant that over time languages become more similar to one another and more complicated.
Go has, thankfully, resisted this process.

@amnonbc  I saw that talk too, and it is a fascinating point.
However, I'm not sure it applies well to this scenario. Golang's fmt library has borrowed sprintf() from C. I would argue that fmt.Sprintf() is more syntactically complicated to the developer than string interpolation. It's not so much that a feature was not borrowed, but rather that a different feature maybe should have been borrowed? Of course, practically, it is virtually impossible to change something as a fundamental as this in a well established language. Therefore, the only practical solution would be to borrow both features, but now we're back at Rob Pike's conundrum. 🤔

#50554 by @Cookie04DE suggests a different approach to this.

Here is how Rust implemented string interpolation this in new 1.58.0 release: Captured identifiers in format strings
let person = get_person();
println!("Hello, {person}!"); // captures the local `person`

println!("Hello, {}!", get_person());                // implicit position
println!("Hello, {0}!", get_person());               // explicit index
println!("Hello, {person}!", person = get_person()); // named

let (width, precision) = get_format();
for (name, score) in get_scores() {
  println!("{name}: {score:width$.precision$}");
}

https://github.com/golang/go/issues/34174#issuecomment-568643567
func (dt DataType) String() string {
	var output string
	spf := func(format string, a ...interface{}) {
		output += fmt.Sprintf(format, a)
	}

	spf(`ThingID=%6d, ThingType=%q`, dt.ThingID, dt.ThingType)
	spf(`, PersonID=%d, PersonDisplayName=%q`, dt.PersonID, dt.PersonDisplayName)
	spf(`, PersonRoomNumber=%q, DateOfBirth=%s`, dt.PersonRoomNumber, dt.DateOfBirth)
	spf(`, Gender=%q, LastViewedBy=%q`, dt.Gender, dt.LastViewedBy)
	spf(`, LastViewDate=%s, SaleCodePrior=%q`, dt.LastViewDate, dt.SaleCodePrior)
	spf(`, SpecialCode=%q, Factory=%q`, dt.SpecialCode, dt.Factory)
	spf(`, Giver=%s, Manager=%q, ServiceDate=%s`, dt.Giver, dt.Manager, dt.ServiceDate)
	spf(`, SessionStart=%s, SessionEnd=%s`, dt.SessionStart, dt.SessionEnd)
	spf(`, SessionDuration=%d, HumanNature=%q`, dt.SessionDuration, dt.HumanNature)
	spf(`, VRCatalog=%v, AdditionTime=%d`, dt.VRCatalog, dt.AdditionTime)
	spf(`, MeteorMagicMuscle=%v, VRQuest=%q`, dt.MeteorMagicMuscle, dt.VRQuest)
	spf(`, SelfCare=%v, BypassTutorial=%q`, dt.SelfCare, dt.BypassTutorial)
	spf(`, MultipleViewsSameday=%v, MMMCode=%q`, dt.MultipleViewsSameday, dt.MMMCode)
	spf(`,%sMMMVoipCommunication=%q`, MMMCodeTail, dt.MMMVoipCommunication)
	spf(`,%sMMMCombatConditions=%q`, MMMVCTail, dt.MMMCombatConditions)
	spf(`,%sMMMSecurityReporting=%q`, MMMCCTail, dt.MMMSecurityReporting)
	spf(`,%sMMMLanguagesKnown=%q`, MMMSRTail, dt.MMMLanguagesKnown)
	spf(`,%sMMMDescription=%q`, MMMLKTail, dt.MMMDescription)
	spf(`,SaleCodeLatest=%q, HonoraryCode=%q`, dt.SaleCodeLatest, dt.HonoraryCode)
	spf(`, LegalCode=%q, CharacterDebuffs=%q`, dt.LegalCode, dt.CharacterDebuffs)
	spf(`,MentalDebuffs=%q, PhysicalDebuffs=%q`, dt.MentalDebuffs, dt.PhysicalDebuffs)
	spf(`,CharacterChallenges=%q`, dt.CharacterChallenges)
	spf(`,CharacterChallengesOther=%q`, dt.CharacterChallengesOther)
	spf(`,CharacterStresses=%q`, dt.CharacterStresses)
	spf(`,RelationshipGoals=%q`, dt.RelationshipGoals)
	spf(`, RelationshipGoalsOther=%q`, dt.RelationshipGoalsOther)
	spf(`,RelationshipLobsters=%q`, dt.RelationshipLobsters)
	spf(`,RelationshipLobstersOther=%q`, dt.RelationshipLobstersOther)
	spf(`,RelationshipLobsterGunslingerDoublePlus=%q`, dt.RelationshipLobsterGunslingerDoublePlus)
	spf(`,RelationshipLobsterGunslingerPlus=%q`, dt.RelationshipLobsterGunslingerPlus)
	spf(`,RelationshipLobsterGunslingerGains=%q`, dt.RelationshipLobsterGunslingerGains)
	spf(`,PersonAcceptsRecognition=%q`, dt.PersonAcceptsRecognition)
	spf(`,PersonAcceptsRecognitionGunslinger=%q`, dt.PersonAcceptsRecognitionGunslinger)
	spf(`,BenefitsFromChocolate=%v`, dt.BenefitsFromChocolate)
	spf(`, DinnerForLovelyWaterfall=%v`, dt.DinnerForLovelyWaterfall)
	spf(`, ModDinners=%q, ModDinnersOther=%q`, dt.ModDinners, dt.ModDinnersOther)
	spf(`,FlexibleHaystackList=%q`, dt.FlexibleHaystackList)
	spf(`, FlexibleHaystackOther=%q`, dt.FlexibleHaystackOther)
	spf(`,ModDiscorseSummary=%q`, dt.ModDiscorseSummary)
	spf(`, MentallySignedBy=%q, Overlord=%q`, dt.MentallySignedBy, dt.Overlord)
	spf(`, PersonID=%d,FactoryID=%q`, dt.PersonID, dt.FactoryID)
	spf(`, DeliveryDate=%s, ManagerID=%q`, dt.DeliveryDate, dt.ManagerID)
	spf(`, ThingReopened=%v`, dt.ThingReopened)

	return output
}

https://github.com/golang/go/issues/34174#issuecomment-568643567
func (dt DataType) String() string {
	var output string
	spf := func(format string, a ...interface{}) {
		output += fmt.Sprintf(format, a)
	}

	spf(`ThingID=%6d, ThingType=%q`, dt.ThingID, dt.ThingType)
	spf(`, PersonID=%d, PersonDisplayName=%q`, dt.PersonID, dt.PersonDisplayName)
	spf(`, PersonRoomNumber=%q, DateOfBirth=%s`, dt.PersonRoomNumber, dt.DateOfBirth)
	spf(`, Gender=%q, LastViewedBy=%q`, dt.Gender, dt.LastViewedBy)
	spf(`, LastViewDate=%s, SaleCodePrior=%q`, dt.LastViewDate, dt.SaleCodePrior)
	spf(`, SpecialCode=%q, Factory=%q`, dt.SpecialCode, dt.Factory)
	spf(`, Giver=%s, Manager=%q, ServiceDate=%s`, dt.Giver, dt.Manager, dt.ServiceDate)
	spf(`, SessionStart=%s, SessionEnd=%s`, dt.SessionStart, dt.SessionEnd)
	spf(`, SessionDuration=%d, HumanNature=%q`, dt.SessionDuration, dt.HumanNature)
	spf(`, VRCatalog=%v, AdditionTime=%d`, dt.VRCatalog, dt.AdditionTime)
	spf(`, MeteorMagicMuscle=%v, VRQuest=%q`, dt.MeteorMagicMuscle, dt.VRQuest)
	spf(`, SelfCare=%v, BypassTutorial=%q`, dt.SelfCare, dt.BypassTutorial)
	spf(`, MultipleViewsSameday=%v, MMMCode=%q`, dt.MultipleViewsSameday, dt.MMMCode)
	spf(`,%sMMMVoipCommunication=%q`, MMMCodeTail, dt.MMMVoipCommunication)
	spf(`,%sMMMCombatConditions=%q`, MMMVCTail, dt.MMMCombatConditions)
	spf(`,%sMMMSecurityReporting=%q`, MMMCCTail, dt.MMMSecurityReporting)
	spf(`,%sMMMLanguagesKnown=%q`, MMMSRTail, dt.MMMLanguagesKnown)
	spf(`,%sMMMDescription=%q`, MMMLKTail, dt.MMMDescription)
	spf(`,SaleCodeLatest=%q, HonoraryCode=%q`, dt.SaleCodeLatest, dt.HonoraryCode)
	spf(`, LegalCode=%q, CharacterDebuffs=%q`, dt.LegalCode, dt.CharacterDebuffs)
	spf(`,MentalDebuffs=%q, PhysicalDebuffs=%q`, dt.MentalDebuffs, dt.PhysicalDebuffs)
	spf(`,CharacterChallenges=%q`, dt.CharacterChallenges)
	spf(`,CharacterChallengesOther=%q`, dt.CharacterChallengesOther)
	spf(`,CharacterStresses=%q`, dt.CharacterStresses)
	spf(`,RelationshipGoals=%q`, dt.RelationshipGoals)
	spf(`, RelationshipGoalsOther=%q`, dt.RelationshipGoalsOther)
	spf(`,RelationshipLobsters=%q`, dt.RelationshipLobsters)
	spf(`,RelationshipLobstersOther=%q`, dt.RelationshipLobstersOther)
	spf(`,RelationshipLobsterGunslingerDoublePlus=%q`, dt.RelationshipLobsterGunslingerDoublePlus)
	spf(`,RelationshipLobsterGunslingerPlus=%q`, dt.RelationshipLobsterGunslingerPlus)
	spf(`,RelationshipLobsterGunslingerGains=%q`, dt.RelationshipLobsterGunslingerGains)
	spf(`,PersonAcceptsRecognition=%q`, dt.PersonAcceptsRecognition)
	spf(`,PersonAcceptsRecognitionGunslinger=%q`, dt.PersonAcceptsRecognitionGunslinger)
	spf(`,BenefitsFromChocolate=%v`, dt.BenefitsFromChocolate)
	spf(`, DinnerForLovelyWaterfall=%v`, dt.DinnerForLovelyWaterfall)
	spf(`, ModDinners=%q, ModDinnersOther=%q`, dt.ModDinners, dt.ModDinnersOther)
	spf(`,FlexibleHaystackList=%q`, dt.FlexibleHaystackList)
	spf(`, FlexibleHaystackOther=%q`, dt.FlexibleHaystackOther)
	spf(`,ModDiscorseSummary=%q`, dt.ModDiscorseSummary)
	spf(`, MentallySignedBy=%q, Overlord=%q`, dt.MentallySignedBy, dt.Overlord)
	spf(`, PersonID=%d,FactoryID=%q`, dt.PersonID, dt.FactoryID)
	spf(`, DeliveryDate=%s, ManagerID=%q`, dt.DeliveryDate, dt.ManagerID)
	spf(`, ThingReopened=%v`, dt.ThingReopened)

	return output
}

I don't know if this is good or not. I think if there is function to get all declared variables like PHP's get_defined_vars(), string interpolation could be implemented as a package and package authors could implement it themselves.

We could add a built in function definedVars() map[string]any that dors the same, though.

The PHP function returns all variables defined anywhere.  That is infeasible in Go.

Yes, but just the variables visible at that point from the current function could be possible. Maybe functionVars() would be a better name then.

I think if there is function to get all declared variables like PHP's get_defined_vars(), string interpolation could be implemented as a package and package authors could implement it themselves.

Reading through this thread I had the same thought (less PHP).
Getting back to the XY problem, it seems to me that @runeimp biggest frustration is the distance between where a value is specified in a format string (be it fmt.Sprintf, os.Expand, or otherwise) and where a value is provided (I.e. positional parameter or map).  I believe the argument that positional parameters introduce cognitive load upon the reader of the code is sound (personally I find it easy to write, but reading such code is a different matter).  I'm not clear as to why using a map is difficult.  My guess would be that building a set of local variables is more natural than building a map (or struct) or converting one or more structs into a single map (or struct).  Then when it comes to producing a new string using your preferred formatting function all the values must restated as either positional arguments, map values or struct fields, which can be tedious and error prone.  A (builtin) function that could capture the variables that are currently in scope obviate this construction of local variables into some other form.
My proposal would be thus:
// capture variables within the current scope and copy their values into I.
func capture(i interface{})

If i is of type map[string]interface{} then everything is copied into the map.  If it is a struct address then only those variables that match the struct's fields are copied.  Anything else results in a compile error.
E.g.
var m map[string]interface{}
s := struct { Foo int }{}
foo := 1
capture(m)
capture(&s)
println(foo, "==", m["foo"], "==", s.Foo)

More attention would need to be paid to handling package variables and mapping variable names to field names (use a special tag?).
I don't know if running user code in the compiler is desirable but a struct method could be called which provides access to local variables from a different scope.  E.g.
// Capture is called by the compiler to populate a struct's fields.
func (a *someStruct) Capture(resolve func(string) interface{}) error {
	a.Foo = resolve("foo").(int)
	return nil
}

Where any error or panic results in a compiler error!

I really like C#s approach for string interpolation.

Strings need a prefix ($"") so that they are interpolated -> Fully backwards compatible
It provides good readability ($"Hello {name}")
It has formatting embedded: {datetime:HH:mm} to show hours:minutes or {num:0.00} to show two decimals

There are a ton more features: csharp string interpolation formating

I regularly check this thread. hope is a b.

Hi guys. It's now 3 (three!) years as I receive updates on this thread in my mailbox. So many, so different, ideas. Many of them are unexpected, many are very smart. So many use cases and situations. It's deep night now, and instead of going home, I read this whole thread, very diligently, from the top down. Why? Because I support my own dialect of Go, and now it's time to finally add this thing there too (as there's little hope in mainstream Go :) So, I thought it'd be good idea to see what people came up with in these three years.
Here's the small digest, for those of you reading this far down the thread. Looks like Go as it is now, plus the ideas and packages presented above, ALREADY has all the tools to solve the problem, in several ways.
Solution 1:

Look at this https://github.com/sirkon/go-format, it already has all the functionality implemented -- but not the ease of syntax, but follow with me...

define these shortcuts:

type MSA map[string]any
// or type MSA format.Values
FS := format.Printm      // or do this with preprocessor, or other way, e.g. as global var

after this, you can write this code:
myStr := FS("${name} $count ${weight|1.2}", MSA{
	"name": "name",
	"count": 12,
	"weight": 0.79,
}

Look at that lib, it's has much more to use. And see also the https://src.eruta.nl/beoran/ginterpol.
Solution 2:
Some genius from Go dev team added os.Expand() into the standard lib. Look at it. Combined with https://github.com/golang/go/issues/34174#issuecomment-759125836, and with the shortcuts from Solution 1, you get an excellent Solution 2, ready to use.
Solution 3:
This is what I am going to implement in my dialect. It's different in that it solves a much broader problem of embedding DSL, it's close in spirit to the     https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals#tagged_templates
. Here's what it'll look like:
name := "You"
 DSL1(`Hello ${name}${p}`, p => "!")

name := "You"
fmt.Print(IT:`
    Hello ${name}!
`)

Where "IT" and "DSL1" are keys, by which particular DSL handlers are registered by user programmer.
Thanks everyone for great analytics and idea, and have a good day (and or night)!

we need this. stings in go are kinda cringe rn

My use case for this revolves around writing code generators in both Go and Kotlin. Kotlin has string interpolation, and Go has fmt.Sprintf() and the text/template package.
For a large textual template, I always use "text/template." In the Go case, every time I have a code generator to write, I need to relearn the text/template` syntax, which is an un-intuitive domain specific language with no editor auto completion or static checks.
In the Kotlin case, there is no need for "text/template" because Kotlin itself can be used to interpolate the template with variable names, type names, etc. I do not re-learn the template formatting language every 6 months when I update the template.
My argument is that widespread use of "text/template" in Go and the absence of a need for that library in Kotlin is compelling evidence that string interpolation is useful.
--
A counterargument might be that most of what I'm doing with string interpolation and text/template could be achieved with simple concatenation:
var code = `var ` + varName + ` = foo()`

is pretty close in convenience to
var code = `var ${varName} = foo()`

Until Go 2.0 does not arrive I have created my own package for string interpolation:
https://github.com/evandrojr/string-interpolation
Simple string interpolation for golang. Interpolates anything in an easy way.
No need to pass the format parameters %d, %s, %t... anymore!
It has 3 methods: Print, Println and Sprint:
Example:
esi.Print("Print ", 10, " ", 7, " interpolates anything ", true, " ", 3.4e10)

Output:
Print 10 7 interpolates anything true 3.4e+10

@evandrojr
fmt.Print(), fmt.Println(), and fmt.Sprint() already work similarly. The only difference is that they insert spaces automatically in certain situations. For example,
fmt.Print("Print ", 10, 7, " interpolates anything ", true, 3.4e10)

is the equivalent of your code above.

@evandrojr
fmt.Print(), fmt.Println(), and fmt.Sprint() already work similarly. The only difference is that they insert spaces automatically in certain situations. For example,
fmt.Print("Print ", 10, 7, " interpolates anything ", true, 3.4e10)

is the equivalent of your code above.
@DeedleFake
Thank you for your answer, I didn't know that!

To be more precise the equivalent are:
	esi.Println("esi.Println ", 10, " ", 7, " interpolates anything ", true, " ", 3.4e10)
	fmt.Print("fmt.Print ", 10, 7, " interpolates anything ", true, 3.4e10, "\n")
	fmt.Println("fmt.Println", 10, 7, "interpolates anything", true, 3.4e10)

I think it causes confusion the difference of Println and Print since Println always add a space between the arguments and Print adds a space only in some situations. I like my solution since it is deterministic because it never adds spaces between the arguments.
I will try to do also:
       i:= 7
	esi.Print("esi.Print {i}")

Output:
esi.Print 7

For me, type-checking as a reason to include string interpolation in the language is not that compelling. There is a more important reason, which is not depending on the order in which you write the variables in fmt.Printf.
Let's take one of the examples from the proposal description and write it in Go with and without string interpolation:

Without string interpolation (current Go)

name := "Mark"
date := time.Now()

fmt.Printf("Hello, %v! Today is %v, it's %02v:%02v now.", name, date.Weekday(), date.Hour(), date.Minute())

Equivalent functionality with string interpolation (and mixing the @bradfitz comment, needed to express the formating options)

name := "Mark"
date := time.Now()

fmt.Printf("Hello, %{name}! Today is %{date.Weekday()}, it's %02{date.Hour()}:%02{date.Minute()} now.")

For me, the second version is easier to read and modify and less error-prone, as we do not depend on the order in which we write the variables.
When reading the first version, you need to re-read the line several times to check it is correct, moving your eyes back and forth to create a mental mapping between the position of a "%v" and the place where you need to put the variable.
So, for a language whose goal is to ease maintainability in the long term, I think this reason makes it worth it to consider having string interpolation
Regarding the complexity of this: I don't know the internals of Go compiler, so take my opinion with a grain of salt, but Couldn't it just translate the second version showed in the above example to the first one?

This proposed solution is quite elegant. Reduces probability of bugs, and includes formatting capabilities that are quite obvious at first glance.

Perhaps it would be useful to consider a simpler approach: #57616 .

Per the discussion in #57616 this is a likely decline.  Leaving open for four weeks for final comments.
You can a similar effect using fmt.Sprint, with custom functions for non-default formatting.  So it can already be done in Go, it just looks a bit different.  fmt.Sprint("This house is ", measurements(2.5), " tall") where measurements is a function that returns strings like "two feet six inches".

No further comments.

Golang templates?  You can't interpolate a var in them either.

{{ func1 "arg1" "arg2 {{ env \"VAR\" }}" }}
{{ func1 "arg1" "arg2 $blah" }}


EXSWHTEC-217 - Implement new and update existing tests for the hipGraph*MemcpyNode family of APIs

Basic positive behavior tests
Negative parameter tests.

@mirza-halilcevic
Will you please resolve the merge conflicts for this PR?

PR has been merged into develop branch via commit cefbaed5cf253ff47d4df5a6c673395483615e25.


Bump base package requirement

What does this PR do?

Bump base package requirement (failing CI: https://dev.azure.com/datadoghq/integrations-core/_build/results?buildId=50064&view=logs&jobId=1b97e13f-8952-5346-8c21-6dbaad23061a).
Related PRs:

kube_controller_manager and kube_scheduler: https://github.com/DataDog/integrations-core/pull/8535
apache: https://github.com/DataDog/integrations-core/pull/8640 (assert_metrics_using_metadata)

Motivation

Additional Notes

Oracle and postgres fails are not related to base package requirement.
Review checklist (to be filled by reviewers)

[ ] Feature or bugfix MUST have appropriate tests (unit, integration, e2e)
[ ] PR title must be written as a CHANGELOG entry (see why)
[ ] Files changes must correspond to the primary purpose of the PR as described in the title (small unrelated changes should have their own PR)
[ ] PR must have changelog/ and integration/ labels attached

Apache is good already: https://github.com/DataDog/integrations-core/pull/8748


colima Waiting for the essential requirement 1 of 4: "ssh"

My pipeline keeps getting stack on: colima Waiting for the essential requirement 1 of 4: "ssh"

Is there any solution for it?

Duplicate of #37. We are trying some stuff there. Seems like some QEMU interactions with the image from Colima are not ending well.


Deleting dataset doesn't delete tables (and creating a table that exists hangs forever)

What happened?
It's common in our tests to bootstrap a dataset, let library code create as many tables as needed, then cleanup the dataset after the test finishes. Removing a dataset should remove all tables and other resources inside it.
The emulator doesn't appear to do this. Additionally, the errors returned from the emulator are 500 which the Google SDKs (at least for Go) retry indefinitely on. This is likely the cause of the deadlock: source / source
What did you expect to happen?
Two things:

When errors happen, return them as non-internalError. The Google SDKs retry on these indefinitely because they're treated as infrastructural errors on their side.
When a dataset is deleted, clean up its tables. It's much easier from a test to delete a single dataset, than to record all tables a library may have created during a test.

How can we reproduce it (as minimally and precisely as possible)?
cd $(mktemp -d)
go mod init test
# Copy below test file to main_test.go
go mod tidy
go test .

package main

import (
	"context"
	"sync"
	"testing"

	"cloud.google.com/go/bigquery"
	"github.com/stretchr/testify/require"
	"github.com/testcontainers/testcontainers-go"
	"github.com/testcontainers/testcontainers-go/wait"
	"google.golang.org/api/option"
)

const (
	bqPort      = "9050/tcp"
	testProject = "project"
)

// startBigQuery spins up a test container and blocks until it's ready. Only one
// container can be started per test binary.
//
// Sharing a container across tests improves suite duration significantly
var startBigQuery = sync.OnceValues(func() (testcontainers.Container, error) {
	ctx := context.Background()
	return testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
		ContainerRequest: testcontainers.ContainerRequest{
			Image: "ghcr.io/goccy/bigquery-emulator:latest",
			Cmd: []string{
				"--project=" + testProject,
			},
			ExposedPorts: []string{bqPort},
			WaitingFor:   wait.ForExposedPort(),
		},
		Started: true,
	})
})

func TestBasic(t *testing.T) {
	t.Run("one", func(t *testing.T) {
		testHelper(t)
	})

	t.Run("two", func(t *testing.T) {
		testHelper(t)
	})
}

func testHelper(t *testing.T) {
	ctx := context.Background()

	// Startup emulator and create client
	container, err := startBigQuery()
	require.NoError(t, err)

	addr, err := container.Endpoint(ctx, "")
	require.NoError(t, err)

	endpoint := "http://" + addr
	client, err := bigquery.NewClient(
		ctx,
		testProject,
		option.WithoutAuthentication(),
		option.WithEndpoint(endpoint),
	)
	require.NoError(t, err)

	// Create dataset and table within
	ds := client.Dataset("main")
	err = ds.Create(ctx, nil)
	require.NoError(t, err)

	// Cleanup entire dataset on test finish
	t.Cleanup(func() {
		require.NoError(t, ds.Delete(ctx))
	})

	t.Log("creating table inside dataset")
	table := ds.Table("data")
	err = table.Create(ctx, &bigquery.TableMetadata{
		Schema: bigquery.Schema{
			&bigquery.FieldSchema{Name: "value", Type: bigquery.NumericFieldType},
		},
	})
	require.NoError(t, err)

	// This prevents the test from deadlocking indefinitely, but removing the entire
	// dataset should be enough.
	//
	// Either that, or returning an appropriate error.
	//
	// t.Cleanup(func() {
	// 	require.NoError(t, table.Delete(ctx))
	// })
	t.Log("table created")
}

func ignoreErr[T any](v T, err error) T {
	if err != nil {
		panic(err)
	}
	return v
}

Anything else we need to know?
No response

Does it work if you use dataset.DeleteWithContents()? Tables are not cleared from datasets during deletion by default.
I agree that the internalError response status causes lots of headaches with client library retries.

@ohaibbq: Oh dang, I completely missed that in the documentation. Thank you very much.
I guess my real issue in with internalError then.


Bundle requires Core, which has dev dependencies

You need a total of 2 Classes from the CoreBundle, which can't be installed unless you cascade your requirements to dev on your whole project.
use CampaignChain\CoreBundle\Bundle\DependencyInjection\Extension;
use CampaignChain\CoreBundle\Util\VariableUtil;
If you have access to these 2 classes in your Update bundle, then it would be fully functional on its own. Right now its not really usable unless you compromise the stability of your project.

Looked a bit further down the code in an attempt to suggest a solution. There are more dependencies, tho it seems for the most part these 2 classes are the major errors for usage as a stand alone.


[WebGPU] Fall back to 256MB for maxBufferSize if needed

By default, we request 1GB of maxStorageBufferBindingSize and maxBufferSize when detecting a WebGPU device. However, low-resource devices such as iOS and Android may not be able to support 1GB.
A previous PR falls back maxStorageBufferBindingSize to 128MB, the default values stated in WGSL doc, motivated by Android Chrome. This PR falls back maxBufferSize to 256MB, the default value, motivated by iOS Safari.

Tested end-to-end on WebLLM with iPhone iOS 18 Beta Safari.


chore: add gradient component with connect page

PR-Codex overview
This PR updates multiple components related to landing pages and wallets. It introduces customizable background props, refactors existing components, and adds new functionalities for better UI customization.
Detailed summary

Added customizable background prop (bg) to components like SupportedPlatformLink, LandingCTAButtons, and LandingHeroWithSideImage.
Refactored components like TrackedLinkButton and LandingEndCTA to include background, border, and hover styles.
Introduced a new Gradients component for managing gradient positions on landing pages.

The following files were skipped due to too many changes: src/pages/connect.tsx

✨ Ask PR-Codex anything about this PR by commenting with /codex {your question}

📝 Changed routes:

/connect
/solutions/chains

(view full size)
(view full size)

Commit 4485168b74f50a03fea93c14c9c108700262a388 (https://thirdweb-bf1moefrh.thirdweb-preview.com).

the assets are pretty huge, any way we can shrink them down?

@jnsdls tried tinypng'ing it, turning into webp, etc but looks really bad
not having loading issues on the page with these currently in it: https://thirdweb.com/solutions/chains
can we merge & i monitor for any traffic/conversion drops?

@jnsdls tried tinypng'ing it, turning into webp, etc but looks really bad
not having loading issues on the page with these currently in it: https://thirdweb.com/solutions/chains
can we merge & i monitor for any traffic/conversion drops?

yes, we can merge it with the larger assets - in general massive assets like this are bad however and we need to find ways to reduce / avoid them

@jnsdls yeah agree - we'll try finding ways to compress + will check with carmen. ty!