diff --git a/glib/gstring.c b/glib/gstring.c
index 2a399ee21..645746c9a 100644
--- a/glib/gstring.c
+++ b/glib/gstring.c
@@ -214,6 +214,38 @@ g_string_new_len (const gchar *init,
     }
 }
 
+/**
+ * g_string_copy:
+ * @string: a string
+ *
+ * Copies the [struct@GLib.String] instance and its contents.
+ *
+ * This will preserve the allocation length of the [struct@GLib.String] in the
+ * copy.
+ *
+ * Returns: (transfer full): a copy of @string
+ * Since: 2.86
+ */
+GString *
+g_string_copy (GString *string)
+{
+  GString *copy = NULL;
+
+  g_return_val_if_fail (string != NULL, NULL);
+
+  copy = g_slice_new (GString);
+  copy->allocated_len = string->allocated_len;
+  copy->len = string->len;
+
+  /* We can’t just strdup(string->str) here because it may contain embedded nuls. */
+  copy->str = g_malloc (string->allocated_len);
+  if (string->str != NULL && string->len > 0)
+    memcpy (copy->str, string->str, string->len);
+  copy->str[copy->len] = '\0';
+
+  return g_steal_pointer (&copy);
+}
+
 /**
  * g_string_free:
  * @string: (transfer full): a #GString
diff --git a/glib/gstring.h b/glib/gstring.h
index 3e66367a0..e817176c9 100644
--- a/glib/gstring.h
+++ b/glib/gstring.h
@@ -58,6 +58,8 @@ GString*     g_string_new_len           (const gchar     *init,
                                          gssize           len);
 GLIB_AVAILABLE_IN_ALL
 GString*     g_string_sized_new         (gsize            dfl_size);
+GLIB_AVAILABLE_IN_2_86
+GString     *g_string_copy              (GString         *string);
 GLIB_AVAILABLE_IN_ALL
 gchar*      (g_string_free)             (GString         *string,
                                          gboolean         free_segment);
diff --git a/glib/tests/string.c b/glib/tests/string.c
index ed7179eb3..fc9d8458a 100644
--- a/glib/tests/string.c
+++ b/glib/tests/string.c
@@ -767,6 +767,40 @@ test_string_new_take_null (void)
   g_string_free (g_steal_pointer (&string), TRUE);
 }
 
+static void
+test_string_copy (void)
+{
+  GString *string1 = NULL, *string2 = NULL;
+
+  string1 = g_string_new ("hello");
+  string2 = g_string_copy (string1);
+  g_assert_cmpstr (string1->str, ==, string2->str);
+  g_assert_true (string1->str != string2->str);
+  g_assert_cmpuint (string1->len, ==, string2->len);
+  g_assert_cmpuint (string2->allocated_len, ==, string2->allocated_len);
+  g_string_free (string1, TRUE);
+  g_string_free (string2, TRUE);
+
+  string1 = g_string_sized_new (100);
+  string2 = g_string_copy (string1);
+  g_assert_cmpstr (string1->str, ==, string2->str);
+  g_assert_true (string1->str != string2->str);
+  g_assert_cmpuint (string1->len, ==, string2->len);
+  g_assert_cmpuint (string2->allocated_len, ==, string2->allocated_len);
+  g_string_free (string1, TRUE);
+  g_string_free (string2, TRUE);
+
+  string1 = g_string_sized_new (200);
+  g_string_append_len (string1, "test with embedded\0nuls", 25);
+  string2 = g_string_copy (string1);
+  g_assert_cmpmem (string1->str, string1->len, string2->str, string2->len);
+  g_assert_true (string1->str != string2->str);
+  g_assert_cmpuint (string1->len, ==, string2->len);
+  g_assert_cmpuint (string2->allocated_len, ==, string2->allocated_len);
+  g_string_free (string1, TRUE);
+  g_string_free (string2, TRUE);
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -796,6 +830,7 @@ main (int   argc,
   g_test_add_func ("/string/steal", test_string_steal);
   g_test_add_func ("/string/new-take", test_string_new_take);
   g_test_add_func ("/string/new-take/null", test_string_new_take_null);
+  g_test_add_func ("/string/copy", test_string_copy);
 
   return g_test_run();
 }