Default parameters and nested struct inferance

Hi, after using Pulumi with Go and TypeScript here’s my thoughts Go’s lack of optional / default parameters, and no ability to infer nested types, keen to see what the community thinks about this.

See below an identical Pulumi project in Go and in TypeScript, which is kicking off an Nginx deployment in K8s

import * as k8s from "@pulumi/kubernetes";

const appLabels = { app: "nginx" };
const deployment = new k8s.apps.v1.Deployment("nginx", {
    spec: {
        selector: { matchLabels: appLabels },
        replicas: 1,
        template: {
            metadata: { labels: appLabels },
            spec: { containers: [{ name: "nginx", image: "nginx" }] }
        }
    }
});
export const name = deployment.metadata.name;
package main

import (
    appsv1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/apps/v1"
    corev1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/core/v1"
    metav1 "github.com/pulumi/pulumi-kubernetes/sdk/v3/go/kubernetes/meta/v1"
    "github.com/pulumi/pulumi/sdk/v3/go/pulumi"
)

func main() {
    pulumi.Run(func(ctx *pulumi.Context) error {

        appLabels := pulumi.StringMap{
            "app": pulumi.String("nginx"),
        }
        deployment, err := appsv1.NewDeployment(ctx, "app-dep", &appsv1.DeploymentArgs{
            Spec: appsv1.DeploymentSpecArgs{
                Selector: &metav1.LabelSelectorArgs{
                    MatchLabels: appLabels,
                },
                Replicas: pulumi.Int(1),
                Template: &corev1.PodTemplateSpecArgs{
                    Metadata: &metav1.ObjectMetaArgs{
                        Labels: appLabels,
                    },
                    Spec: &corev1.PodSpecArgs{
                        Containers: corev1.ContainerArray{
                            corev1.ContainerArgs{
                                Name:  pulumi.String("nginx"),
                                Image: pulumi.String("nginx"),
                            }},
                    },
                },
            },
        })
        if err != nil {
            return err
        }

        ctx.Export("name", deployment.Metadata.Elem().Name())

        return nil
    })
}

The TypeScript version is so much clearer that it makes it impossible to select Go as the backend language, for this type of IaaC project I want someone to look at the code and instantly know what they’re looking at.

The two problems here are:

  1. Pulumi makes heavy use of the optional parameter workaround, so you have to use the hack of making a value into a reference so it can be nil. This isn’t poor design IMO, but a good choice for an API where you want sensible defaults, so you only change what you need to.
  2. Nested structs aren’t inferred, so you have to name every nested struct causing a lot of clutter for a huge project like this, where you need longer descriptive struct names.

This type of thing is very common, e.g. using cloud provider API’s like AWS, I see GCP has expertise with Go and so have much flatter structures, and design in a way where they don’t need to use the optional parameter hack, but still things like GKE have deeply nested structs that are cluttered.

I really like Go for its simplicity, almost all of the cloud tools I use now are written in Go, and I would also like to use it for IaaC, but right now TypeScript is a far better choice. I think these two things while adding a small amount of complexity for API designers GREATLY reduce the complexity for API consumers.