Characters and Strings
|
This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. The reference material consulted while preparing these pages is listed in the section bibliography. |
This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3 final draft J3/23-007r1) and was checked on 2026-10-18
with GNU Fortran 16.2 and LLVM Flang 23. A Fortran string is not an array of characters and not an object with methods: it is
a scalar of type character whose length is a type parameter, and nearly everything about strings follows from that. The
length decides whether an assignment pads or truncates, whether two strings are equal, whether a variable can change size at
run time and whether the compiler can check a call. The page follows that thread: the character type and its declaration
forms, literals and substrings, assignment and comparison, the four ways to give a string a length, the string intrinsics,
the Fortran 2023 intrinsics split and tokenize, conversion between numbers and strings, the Fortran 2023 at edit
descriptor and the automatic allocation of iomsg variables, character kinds and UTF-8, C strings, arrays of strings and the
string_type of the Fortran standard library.
Several Fortran 2023 string features are not implemented by both compilers. Every such feature below is labelled with the compiler that accepts it and the real diagnostic or failure of the one that does not, and the portable replacement is shown next to it. The compiler-support table at the end of the page collects them. Format specifications and internal files are covered on Format Specifications and Edit Descriptors and Internal Files, Namelist and Non-Advancing I/O; the kind system on Intrinsic Types and Kinds; allocation on Allocatable Arrays and Dynamic Memory; and the C side of strings on Interoperability with C.
Every example belongs to the heatflow scenario (a two-dimensional heat-diffusion solver) and uses its vocabulary: boundary
condition names, solver names, namelist-style key=value specifications and CSV file names. Each one is a complete,
self-contained file. Unless a paragraph says otherwise, every example compiles without warnings under
gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all, was also built and run with LLVM Flang 23, and the
output shown is what both printed. Examples that are deliberately wrong, or that use a feature one compiler lacks, are
labelled in the text and in a comment on their first line, and the compiler messages or run-time failures they produce are
shown.
The character type
The type character has a set of values that are strings of characters. A string is a sequence of characters numbered from
1, and its length is the number of characters, which may be zero. The length is a type parameter, written
character(len=n); the other type parameter is the kind, which selects the character set and its representation
(J3/23-007r1, 7.4.4.1 and 7.4.4.2). The default kind is the one of the literal 'a', and on both compilers used here it
holds one byte per character; kinds for other character sets are the subject of the section on character kinds below.
The declaration forms are the following. They differ in who decides the length, and when.
| Form | Length | Where it is allowed |
|---|---|---|
|
1. |
Everywhere. A missing length means 1, so |
|
Fixed at compile time by a constant expression. |
Variables, components, function results, named constants. |
|
Automatic length: evaluated each time the procedure is entered, from a specification expression such as |
Local variables and function results of a procedure; not module variables. |
|
Assumed length: taken from the actual argument at each call. |
Dummy arguments, named constants ( |
|
Deferred length: set by an |
Variables, components, function results, dummy arguments (Fortran 2003). |
|
Deferred length taken from the pointer target. |
Pointers; see Pointers and Targets. |
|
Same as |
Obsolescent: Annex B.3.8 of J3/23-007r1 calls the form redundant. Appears only in old code. |
Three rules of that clause are easy to miss. A negative length is not an error: the entity has length zero. A length of *
is not allowed for a local variable. And an entity can carry its own length that overrides the one in the type specifier
(character(len=8) :: a, b*20), which is legal and best avoided.
A character literal is delimited by apostrophes or quotation marks, and the delimiter inside the literal is written twice:
'It''s' and "It's" are the same string. The length of a literal is the number of characters between the delimiters, a
literal of a kind other than the default is written with the kind name and an underscore
before the delimiter (ucs4_'text'), and the empty literal
'' has length zero. In free source form a character literal can continue across lines with & at the end of the line and
& at the start of the next, and Fortran 2023 raises the limit on line length to 10,000 characters (see
Program Structure and Source Form).
The first example declares the main forms, shows what an assignment does when the lengths differ, and uses substrings, which
the next section explains. Its helper store has two assumed-length dummies, so the lengths are known only at run time and the
compiler cannot warn about the truncation it performs.
module str_helpers
implicit none
contains
! Both lengths are known only at run time, so the compiler cannot warn about truncation.
subroutine store(dst, src)
character(len=*), intent(out) :: dst
character(len=*), intent(in) :: src
dst = src ! padded with blanks if src is shorter, truncated if it is longer
end subroutine store
end module str_helpers
program str_basics
use str_helpers, only: store
implicit none
character(len=8) :: solver ! fixed length: always exactly 8 characters
character(8) :: same_thing ! shorthand for len=8
character :: one ! len=1 by default
character(len=0) :: nothing ! legal: a zero-length string
character(len=*), parameter :: banner = "heatflow's solver" ! length taken from the constant
character(len=20) :: title
call store(solver, 'ftcs') ! padded: 'ftcs '
call store(same_thing, 'jacobi-method') ! truncated on the right: 'jacobi-m'
call store(one, 'xyz') ! keeps 'x'
call store(nothing, 'abc') ! keeps nothing
title = 'It''s "hot"' ! doubled delimiter inside a literal
print '(a, i0)', 'len(solver) = ', len(solver)
print '(a, i0)', 'len_trim(solver) = ', len_trim(solver)
print '(3a)', '[', solver, ']'
print '(3a)', '[', same_thing, ']'
print '(3a)', '[', one, ']'
print '(a, i0)', 'len(nothing) = ', len(nothing)
print '(a, i0)', 'len(banner) = ', len(banner)
print '(3a)', '[', title, ']'
print '(a, i0)', 'len(title) = ', len(title)
print '(a, i0)', 'len_trim(title) = ', len_trim(title)
print '(3a)', '[', solver(2:3), ']' ! substring
print '(3a)', '[', banner(:8), ']' ! start defaults to 1
print '(3a)', '[', banner(10:), ']' ! end defaults to len
print '(a, i0)', 'len(solver(5:4)) = ', len(solver(5:4)) ! start > end: length zero
print '(3a)', '[', 'abcdef'(2:4), ']' ! substring of a literal
solver(1:1) = 'F' ! assignment to a substring
print '(3a)', '[', solver, ']'
end program str_basics
len(solver) = 8
len_trim(solver) = 4
[ftcs ]
[jacobi-m]
[x]
len(nothing) = 0
len(banner) = 17
[It's "hot" ]
len(title) = 20
len_trim(title) = 10
[tc]
[heatflow]
[s solver]
len(solver(5:4)) = 0
[bcd]
[Ftcs ]
Writing the same assignments with constants of known length, same_thing = 'jacobi-method', makes GNU Fortran warn
CHARACTER expression will be truncated in assignment (8/13) under -Wall (the option is -Wcharacter-truncation); Flang
does not warn. The truncation is not an error in either case, which is why a dedicated procedure that checks
len_trim(src) > len(dst) is a better way to copy data that must not be cut.
Substrings
A substring is a contiguous part of a string, written parent(first:last). The parent can be a variable, an array element,
a structure component, a coindexed object or a constant. Both bounds are optional: the first defaults to 1 and the last to
the length of the parent. The length of the substring is max(last - first + 1, 0). If first is greater than last the
substring has length zero, and that is legal for any values, even outside the string; otherwise both bounds must lie between
1 and the length of the parent (J3/23-007r1, 9.4.1). A substring is a variable when its parent is, so it can be assigned,
passed to a procedure and used as an internal file. As the standard’s own note points out, the right-hand side is evaluated
before any part of the left-hand side is defined, so overlapping assignment such as s(2:5) = s(1:4) behaves as if a
temporary were used.
The bounds are not checked by default. A substring that runs past the end of the string reads or writes the neighbouring
memory. With -fcheck=all (or -fcheck=bounds) GNU Fortran detects it at run time:
! Deliberately non-conforming: the substring end point exceeds the length of the string.
program substring_overrun
implicit none
character(len=8) :: solver
integer :: last
solver = 'ftcs'
last = 12 ! not a constant, so the compiler cannot reject it at compile time
print '(3a)', '[', solver(5:last), ']'
end program substring_overrun
At line 8 of file substring_overrun.f90
Fortran runtime error: Substring out of bounds: upper bound (12) of 'solver' exceeds string length (8)
Error termination. Backtrace:
LLVM Flang 23 has no such check in the build used here, built with its default options. The program ran to completion and the substring was printed with the bytes that follow the 8-byte variable in memory (the output contained non-printable characters). Only a compiler option that inserts the check, or a tool such as a sanitizer, finds the error; this is the same situation as for array bounds, discussed on Testing, Debugging and Profiling.
Assignment, concatenation and comparison
Assignment to a character variable of fixed length copies the value; if the value is shorter it is extended on the right
with blanks, and if it is longer it is truncated from the right (J3/23-007r1, 10.2.1.3). The padding character of a
nondefault kind is processor dependent. Concatenation uses the operator //, which joins two strings of the same kind into a
string whose length is the sum of the lengths (10.1.5.3). It is the only character operator, it is associative, and the
precedence of // is lower than that of the arithmetic operators and higher than that of the relational ones, so
'n = ' // to_string(n) // ' steps' needs no parentheses.
Comparison with ==, /=, <, <=, > and >= (or their older spellings .eq. and so on) compares one character at a
time from the left. If the operands have unequal length, the shorter one is treated as if it were extended with blanks on the
right to the length of the longer one (J3/23-007r1, 10.1.5.5.1). Two consequences matter every day:
-
Trailing blanks never affect a comparison:
'ftcs'equals'ftcs ', and a zero-length string equals any string of blanks. Leading blanks do count. -
The padding character takes part in the ordering. Because the shorter operand is padded with a blank,
'a'compares as'a ', so it is less than'a!'(the blank, code 32, precedes!, code 33) and greater than a string whose next character has a code below 32, such as'a'//char(1).
The order of two different characters is given by the collating sequence. For the ASCII character kind it is the ASCII
sequence and for the ISO 10646 kind it is the order of the code points (7.4.4.4). For default character the sequence is
processor dependent, but the equality operators do not depend on it. The four intrinsic functions lge, lgt, lle and llt
compare with the ASCII sequence whatever the processor uses, and the standard guarantees portability for the Fortran
character set. They exist because, on a processor whose default sequence is not ASCII, < and lgt could differ.
program str_compare
implicit none
character(len=8) :: a, b
character(len=:), allocatable :: c
a = 'ftcs'
b = 'ftcs '
c = 'ftcs'
print '(a, l1)', "a == b : ", a == b ! trailing blanks never matter
print '(a, l1)', "a == c : ", a == c ! c has length 4, a has length 8
print '(a, l1)', "'ab' == 'ab ' : ", 'ab' == 'ab '
print '(a, l1)', "'ab' == ' ab' : ", 'ab' == ' ab' ! leading blanks do matter
print '(a, l1)', "'' == ' ' : ", '' == ' ' ! a zero-length string equals blanks
print '(a, l1)', "'Z' < 'a' : ", 'Z' < 'a' ! ASCII: upper case precedes lower case
print '(a, l1)', "'a' < 'B' : ", 'a' < 'B'
print '(a, l1)', "'a' < 'a!' : ", 'a' < 'a!' ! shorter operand is padded with a blank (code 32)
print '(a, l1)', "'a' < 'a'//char(1): ", 'a' < 'a'//char(1)
print '(a, l1)', "lgt('b', 'A') : ", lgt('b', 'A')
print '(a, l1)', "lle('abc', 'abd') : ", lle('abc', 'abd')
print '(a, l1)', "a(1:4) == c : ", a(1:4) == c
end program str_compare
a == b : T
a == c : T
'ab' == 'ab ' : T
'ab' == ' ab' : F
'' == ' ' : T
'Z' < 'a' : T
'a' < 'B' : F
'a' < 'a!' : T
'a' < 'a'//char(1): F
lgt('b', 'A') : T
lle('abc', 'abd') : T
a(1:4) == c : T
Equality with trailing blanks ignored is convenient for fixed-length fields and a trap for deferred-length ones: after
name = 'ftcs ' the length of name is 7, name == 'ftcs' is true, and yet name and trim(name) have different lengths. Case
is significant in comparisons. There is no case-insensitive comparison in the standard; the section on intrinsics shows a
portable one built from iachar and achar.
Four ways to give a string a length
A procedure that handles strings has to decide how the length is determined. The choice follows from who knows it and whether it changes.
The next two subsections show automatic length and deferred length, the two forms that are newest to people coming from
other languages; assumed length appears in every example because every helper takes character(len=*) arguments.
Assumed and automatic length
An assumed-length dummy argument has the length of the actual argument, and len tells the procedure what it is. The
procedure does not own the memory, so it can neither lengthen the string nor see how much of the actual argument is
significant: it sees blanks, and len_trim is the way to ignore the trailing ones. A function result cannot be of assumed
length in new code (the form is obsolescent, Annex B.3.6); the replacement is an automatic length, a length computed from the
arguments by a specification expression. This is the usual shape of a function that transforms a string and returns a string of
the same size:
module heatflow_text
implicit none
private
public :: to_upper, same_name, reversed, is_digit
contains
! Automatic length: the result length is a specification expression, here len(text).
pure function to_upper(text) result(upper)
character(len=*), intent(in) :: text
character(len=len(text)) :: upper
integer :: i, code
upper = text
do i = 1, len(text)
code = iachar(text(i:i))
if (code >= iachar('a') .and. code <= iachar('z')) upper(i:i) = achar(code - 32)
end do
end function to_upper
! Case-insensitive comparison; trailing blanks are ignored by == anyway.
pure logical function same_name(a, b)
character(len=*), intent(in) :: a, b
same_name = to_upper(a) == to_upper(b)
end function same_name
pure function reversed(text) result(back)
character(len=*), intent(in) :: text
character(len=len(text)) :: back
integer :: i, n
n = len(text)
do i = 1, n
back(i:i) = text(n - i + 1:n - i + 1)
end do
end function reversed
! An elemental function over characters works on whole arrays of characters.
elemental logical function is_digit(c)
character(len=1), intent(in) :: c
is_digit = c >= '0' .and. c <= '9'
end function is_digit
end module heatflow_text
program auto_length
use heatflow_text, only: to_upper, same_name, reversed, is_digit
implicit none
character(len=*), parameter :: spec = 'nx=41,ny=21'
character(len=:), allocatable :: s
integer :: i
s = 'Dirichlet'
print '(3a)', '[', to_upper(s), ']'
print '(3a)', '[', to_upper('neumann '), ']' ! trailing blanks are kept: length is len(text)
print '(a, l1)', "same_name('Periodic', 'PERIODIC '): ", same_name('Periodic', 'PERIODIC ')
print '(3a)', '[', reversed(s), ']'
print '(a, i0)', 'digits in spec: ', count(is_digit([(spec(i:i), i = 1, len(spec))]))
end program auto_length
[DIRICHLET]
[NEUMANN ]
same_name('Periodic', 'PERIODIC '): T
[telhciriD]
digits in spec: 4
to_upper('neumann ') has the length of its argument, so the two trailing blanks survive. A function whose result length is
not known from the arguments, such as one that reads or builds text, uses a deferred-length result instead.
Deferred length
A variable declared character(len=:), allocatable has no length until it is allocated, and an intrinsic assignment
s = expr allocates it with the length of expr if it is unallocated, and deallocates and reallocates it if the lengths
differ (J3/23-007r1, 10.2.1.3, with the standard’s own note for NAME = 'Dr. ' // FIRST_NAME // SURNAME). This
automatic reallocation is on by default in GNU Fortran for every standard from Fortran 2003 on (the manual’s option is
-frealloc-lhs). Four things are worth stating exactly:
-
s(:) = exprdoes not reallocate. A substring or section on the left keeps the existing length: the value is padded or truncated, and the variable must already be allocated.s(1:3) = texton an unallocated variable is an error. -
allocate (character(len=n) :: s)allocates with an explicit length andallocate (s, source=expr)allocates with the length and value ofexpr;mold=exprtakes only the length. After a plainallocatethe content is undefined. -
call move_alloc(from, to)transfers the allocation, length included, without copying. -
Elements of a deferred-length array share one length.
character(len=:), allocatable :: a(:)has a singlelen(a)for all elements; for strings of different lengths see the section on arrays of strings.
The following program uses the function that heatflow_boundary provides, boundary_label, with a deferred-length result,
and shows each of the points above:
module heatflow_boundary_names
implicit none
private
public :: boundary_label, describe
contains
! A function result with deferred length: the length is whatever the result is assigned.
pure function boundary_label(code) result(label)
integer, intent(in) :: code
character(len=:), allocatable :: label
select case (code)
case (1)
label = 'dirichlet'
case (2)
label = 'neumann'
case (3)
label = 'periodic'
case default
label = 'unknown'
end select
end function boundary_label
! An assumed-length dummy accepts any length; len(text) is the caller's length.
subroutine describe(name, text)
character(len=*), intent(in) :: name
character(len=*), intent(in) :: text
print '(4a, i0, a, i0)', name, ' = [', text, '] len=', len(text), ' len_trim=', len_trim(text)
end subroutine describe
end module heatflow_boundary_names
program deferred_length
use heatflow_boundary_names, only: boundary_label, describe
implicit none
character(len=:), allocatable :: s, t, u
character(len=:), allocatable :: pieces(:)
integer :: n
print '(a, l1)', 'allocated(s) before assignment: ', allocated(s)
s = 'ftcs' ! automatic allocation with length 4
call describe('s', s)
s = s // ' solver' ! reallocated to length 11
call describe('s', s)
s = boundary_label(1) ! reallocated to length 9
call describe('s', s)
s(:) = 'neu' ! no reallocation: the old length is kept, padding added
call describe('s(:)', s)
s = boundary_label(3)
s(:) = 'a very long replacement' ! no reallocation: truncated to the existing length
call describe('s(:)', s)
n = 5
allocate (character(len=n) :: t) ! explicit length at allocation; t is undefined
print '(a, i0)', 'len(t) after allocate = ', len(t)
t = 'abcdefgh' ! reallocated on assignment: the length 5 becomes 8
call describe('t', t)
deallocate (t)
allocate (t, source=boundary_label(2)) ! length and value taken from the source expression
call describe('t', t)
call move_alloc(t, u) ! u takes over the allocation, t is now unallocated
print '(a, l1, a, l1)', 'allocated(t)=', allocated(t), ' allocated(u)=', allocated(u)
call describe('u', u)
allocate (character(len=9) :: pieces(3)) ! all elements of one array share one length
pieces = [character(len=9) :: 'dirichlet', 'neumann', 'periodic']
print '(a, i0, a, i0)', 'len(pieces)=', len(pieces), ' size(pieces)=', size(pieces)
do n = 1, size(pieces)
print '(i0, 2a)', n, ': ', trim(pieces(n))
end do
end program deferred_length
allocated(s) before assignment: F
s = [ftcs] len=4 len_trim=4
s = [ftcs solver] len=11 len_trim=11
s = [dirichlet] len=9 len_trim=9
s(:) = [neu ] len=9 len_trim=3
s(:) = [a very l] len=8 len_trim=8
len(t) after allocate = 5
t = [abcdefgh] len=8 len_trim=8
t = [neumann] len=7 len_trim=7
allocated(t)=F allocated(u)=T
u = [neumann] len=7 len_trim=7
len(pieces)=9 size(pieces)=3
1: dirichlet
2: neumann
3: periodic
A deferred-length variable must be allocated before it is read, and a substring assignment never allocates:
! Deliberately non-conforming: the deferred-length variable is used before it is allocated.
program unallocated_string
implicit none
character(len=:), allocatable :: s
character(len=:), allocatable :: t
t = 'abc'
s(1:3) = t ! substring assignment never allocates
print '(3a)', '[', s, ']'
end program unallocated_string
unallocated_string.f90:8:3:
8 | s(1:3) = t ! substring assignment never allocates
| ^~~~~~~
Warning: '.s' may be used uninitialized [-Wmaybe-uninitialized]
unallocated_string.f90:2:26:
2 | program unallocated_string
| ^
note: '.s' was declared here
At line 8 of file unallocated_string.f90
Fortran runtime error: Substring out of bounds: upper bound (3) of 's' exceeds string length (0)
Error termination. Backtrace:
LLVM Flang 23 ended the same program with Segmentation fault. The diagnostic from GNU Fortran comes from -fcheck=all;
without it the program writes through a null pointer, and the -Wmaybe-uninitialized warning above is the only compile-time
hint.
A dummy argument with the allocatable attribute and a deferred length lets a procedure replace the caller’s string:
character(len=:), allocatable, intent(out) :: text is deallocated on entry and the procedure assigns it. The interface must be
explicit (a module procedure is the simplest way), and the actual argument must itself be a deferred-length allocatable; a
fixed-length actual argument is rejected:
! Deliberately non-conforming call: a fixed-length actual argument for a deferred-length dummy.
module m
contains
subroutine make_label(text)
character(len=:), allocatable, intent(out) :: text
text = 'dirichlet'
end subroutine make_label
end module m
program alloc_dummy
use m, only: make_label
implicit none
character(len=:), allocatable :: ok
character(len=20) :: fixed
call make_label(ok)
print '(a, i0)', 'len(ok)=', len(ok)
call make_label(fixed)
end program alloc_dummy
GNU Fortran 16.2, then LLVM Flang 23:
alloc_dummy.f90:17:18:
17 | call make_label(fixed)
| 1
Error: Actual argument at (1) to allocatable or pointer dummy argument 'text' must have a deferred length type parameter if and only if the dummy has one
error: Semantic errors in alloc_dummy.f90
alloc_dummy.f90:17:19: error: ALLOCATABLE dummy argument 'text=' must be associated with an ALLOCATABLE actual argument
call make_label(fixed)
^^^^^
An intent(in) assumed-length dummy accepts both fixed-length and allocatable actual arguments and is the better choice for
strings the procedure only reads.
A last length-related error is passing a shorter actual argument to a dummy of explicit length. It is reported when the compiler can see both lengths, and silently ignored otherwise:
! Deliberately non-conforming: the actual argument is shorter than the explicit-length dummy.
module m
contains
subroutine show(text)
character(len=20), intent(in) :: text
print '(3a)', '[', text, ']'
end subroutine show
end module m
program short_actual
use m, only: show
implicit none
character(len=10) :: name
name = 'ftcs'
call show(name)
end program short_actual
short_actual.f90:15:12:
15 | call show(name)
| 1
Error: Character length of actual argument shorter than of dummy argument 'text' (10/20) at (1)
LLVM Flang 23 accepted the program without a diagnostic and printed the 10 bytes of name followed by whatever followed them in
memory. Use assumed length for dummy arguments that only read a string.
String intrinsic procedures
The intrinsic functions below are all elemental, so they apply to arrays of strings element by element, except where the table says otherwise. The Since column is the Fortran standard that introduced the procedure or the argument named.
| Procedure | Clause | Since | Result |
|---|---|---|---|
|
16.9.122 |
F77 |
Length of the string. For an unallocated variable of deferred length it is undefined. The |
|
16.9.123 |
F90 |
Length without trailing blanks; 0 for a string of blanks. |
|
16.9.214 |
F90 |
The string without trailing blanks, with the length |
|
16.9.8, 16.9.9 |
F90 |
Same length: leading blanks are removed and put at the end ( |
|
16.9.109 |
F77 |
Start of the first ( |
|
16.9.179 |
F90 |
Position of the first (last with |
|
16.9.218 |
F90 |
Position of the first (last with |
|
16.9.174 |
F90 |
|
|
16.9.3, 16.9.98 |
F90 |
Conversion between an ASCII code and its character, independent of the processor’s default sequence. |
|
16.9.52, 16.9.105 |
F77 |
Conversion using the processor’s own code for the kind. For codes 0 to 127 on a processor whose default kind is ASCII they equal |
|
16.9.124 to 16.9.127 |
F77 |
Comparison using the ASCII sequence. |
|
16.9.150 |
F2003 |
The newline character of the kind of |
|
16.9.180 |
F2003 |
Kind for |
|
16.9.196 |
F2023 |
Subroutine: finds the next delimiter. See the next section. |
|
16.9.210 |
F2023 |
Subroutine: all tokens at once. See the next section. |
|
16.9.135 to 16.9.144 and 16.9.88 |
see text |
Accept character arguments and use the collating comparison. |
|
16.9 |
F90 |
Work on arrays and strings of any character type, which is how a string is converted to and from an array of single characters. |
Source: J3/23-007r1 clause 16.9 (Specifications of the standard intrinsic procedures) and Fortran Programming Language: Intrinsics, Characters
The program below exercises each of the first group, including the edge cases in the table (the empty substring, the zero
copies of repeat, the three outcomes of verify). The case conversion at the end is the portable idiom, since there is no
intrinsic for it:
program str_intrinsics
implicit none
character(len=32) :: path
character(len=:), allocatable :: key, text
character(len=*), parameter :: digits = '0123456789'
integer :: i
path = ' output/heatflow.csv'
print '(a, i0, a, i0)', 'len=', len(path), ' len_trim=', len_trim(path)
print '(3a)', '[', trim(path), ']' ! removes trailing blanks only
print '(3a)', '[', adjustl(path), ']' ! moves leading blanks to the end
print '(3a)', '[', adjustr('ftcs '), ']' ! moves trailing blanks to the front
print '(3a)', '[', trim(adjustl(path)), ']' ! the usual idiom
text = 'bc=dirichlet,solver=ftcs,solver=jacobi'
print '(a, i0)', "index(text, 'solver') = ", index(text, 'solver')
print '(a, i0)', "index(text, 'solver', back=.true.) = ", index(text, 'solver', back=.true.)
print '(a, i0)', "index(text, 'missing') = ", index(text, 'missing')
print '(a, i0)', "index(text, '') = ", index(text, '')
print '(a, i0)', "scan(text, ',=') = ", scan(text, ',=') ! first of any listed character
print '(a, i0)', "scan(text, ',=', back=.true.) = ", scan(text, ',=', back=.true.)
print '(a, i0)', "verify('2000', digits) = ", verify('2000', digits) ! 0: every character is a digit
print '(a, i0)', "verify('20x0', digits) = ", verify('20x0', digits) ! position of the first non-digit
print '(a, i0)', "verify('20x0 ', digits, .true.)= ", verify('20x0 ', digits, .true.)
print '(2a)', 'repeat: ', repeat('=-', 6)
print '(2a)', 'repeat with 0 copies: [', repeat('ab', 0) // ']'
key = 'Solver'
print '(a, i0, a, a)', 'iachar(', iachar(key(1:1)), ') = ', key(1:1)
print '(a, a)', 'achar(97 + 1) = ', achar(97 + 1)
print '(a, i0)', "ichar('A') = ", ichar('A')
print '(a, a)', "char(66) = ", char(66)
do i = 1, len(key) ! upper-case conversion with ASCII codes
if (key(i:i) >= 'a' .and. key(i:i) <= 'z') key(i:i) = achar(iachar(key(i:i)) - 32)
end do
print '(2a)', 'upper-cased: ', key
print '(a)', 'line one' // new_line('a') // 'line two'
print '(a, i0)', "iachar(new_line('a')) = ", iachar(new_line('a'))
end program str_intrinsics
len=32 len_trim=22
[ output/heatflow.csv]
[output/heatflow.csv ]
[ ftcs]
[output/heatflow.csv]
index(text, 'solver') = 14
index(text, 'solver', back=.true.) = 26
index(text, 'missing') = 0
index(text, '') = 1
scan(text, ',=') = 3
scan(text, ',=', back=.true.) = 32
verify('2000', digits) = 0
verify('20x0', digits) = 3
verify('20x0 ', digits, .true.)= 5
repeat: =-=-=-=-=-=-
repeat with 0 copies: []
iachar(83) = S
achar(97 + 1) = b
ichar('A') = 65
char(66) = B
upper-cased: SOLVER
line one
line two
iachar(new_line('a')) = 10
Source: J3/23-007r1 clauses 16.9.3, 16.9.8, 16.9.9, 16.9.98, 16.9.109, 16.9.150, 16.9.174, 16.9.179 and 16.9.218, GNU Fortran manual: INDEX and GNU Fortran manual: ADJUSTL
trim and adjustl are the pair behind most of the code in this page: trim(adjustl(buffer)) turns a fixed-length buffer
that was written with a format into the value without padding. The functions that take a set (scan, verify) treat it as a
set of characters, not as a substring; scan(text, ',=') finds the first comma or equals sign, and index(text, ',=') would
look for the two-character sequence.
The ordering intrinsics work on strings too. maxval and minval of an array of strings return the greatest and least in the
collating sequence, maxloc and minloc give the position, and max and min of several strings return the largest
extended with blanks to the longest length:
program char_maxmin
implicit none
character(len=9) :: names(3) = [character(len=9) :: 'jacobi', 'ftcs', 'multigrid']
print '(3a)', '[', maxval(names), ']'
print '(3a)', '[', minval(names), ']'
print '(a, i0, a, i0)', 'maxloc=', maxloc(names, 1), ' minloc=', minloc(names, 1)
print '(3a)', '[', max('ftcs', 'jacobi', 'abc'), ']'
end program char_maxmin
[multigrid]
[ftcs ]
maxloc=3 minloc=2
[jacobi]
Source: J3/23-007r1 clauses 16.9.135 (MAX), 16.9.138 (MAXVAL), 16.9.137 (MAXLOC) and 16.9.88 (FINDLOC)
Because a string is a scalar, string(i) is not an element access and string(i:i) is how a single character is read. When an
algorithm is simpler on an array, transfer(string, [' ']) converts a string to an array of character(len=1), and
transfer(array, repeat(' ', n)) converts back. The pack call below also shows that chars /= ',' is an elemental
comparison on single characters:
program char_arrays
implicit none
character(len=*), parameter :: spec = 'nx=41,ny=21'
character(len=1), allocatable :: chars(:)
character(len=:), allocatable :: digits_only, back
integer :: i
chars = transfer(spec, [' ']) ! string -> array of character(len=1)
print '(a, i0, a, i0)', 'size(chars)=', size(chars), ' len(spec)=', len(spec)
digits_only = ''
do i = 1, size(chars)
if (index('0123456789', chars(i)) > 0) digits_only = digits_only // chars(i)
end do
print '(2a)', 'digits: ', digits_only
back = transfer(pack(chars, chars /= ','), repeat(' ', count(chars /= ','))) ! array -> string
print '(2a)', 'without commas: ', back
print '(a, i0)', 'commas: ', count(chars == ',')
print '(a, i0)', 'first "=" at element ', findloc(chars, '=', 1)
print '(a, *(a))', 'reversed: ', chars(size(chars):1:-1)
end program char_arrays
size(chars)=11 len(spec)=11
digits: 4121
without commas: nx=41ny=21
commas: 1
first "=" at element 3
reversed: 12=yn,14=xn
Splitting a string: split and tokenize
Parsing key=value lists, comma-separated numbers and file paths means cutting a string at delimiters. Before Fortran 2023
this was written by hand with index and scan, as in the portable function later in the page. Fortran 2023 added two
intrinsic subroutines for the task. In both, set is a string whose characters are all delimiters, and a token is the text
between two delimiters or between a delimiter and an end of the string. Consecutive delimiters, or a delimiter at either end,
produce a token of length zero; nothing is discarded or merged.
-
split(string, set, pos [, back])finds one delimiter at a time. On entrypossays where to start (0 to start at the beginning), on return it is the position of the next delimiter, orlen(string)+1if there is none. The token that was just passed runs from the oldpos+1to the newpos-1. Withback=.true.the search goes right to left:posstarts atlen(string)+1and returns the previous delimiter or 0. No memory is allocated, so it suits loops and fixed-size buffers. -
tokenize(string, set, tokens [, separator])finds all tokens and returns them in an allocatable, rank-one, deferred-length array whose length is that of the longest token, so shorter tokens are padded with blanks. The optionalseparatorreceives the delimiter that follows each token. The second form,tokenize(string, set, first, last), allocates two integer arrays with the start and end positions of every token; a zero-length token haslast = first - 1. Both forms allocate the arguments they define.
Both subroutines are simple procedures (J3/23-007r1, 16.9.196 and 16.9.210), which means they can be called from do concurrent
and from other pure procedures. The loops below use split; the start of the loop is always pos = 0 and the test is pos >
len(string), so a trailing delimiter correctly produces a final empty token. Both a forward and a backward scan are shown,
with a string that has empty tokens at both ends:
program split_loop
implicit none
character(len=*), parameter :: delimiters = ',;'
call show_forward('nx=41,,ny=21;alpha=1.0')
call show_forward(',a,') ! empty tokens at both ends
call show_backward('nx=41,,ny=21;alpha=1.0')
contains
subroutine show_forward(spec)
character(len=*), intent(in) :: spec
integer :: first, pos, count
print '(3a)', 'forward: "', spec, '"'
pos = 0 ! 0 means: before the first character
count = 0
do
if (pos > len(spec)) exit ! pos = len+1 after the last token
first = pos + 1
call split(spec, delimiters, pos) ! pos becomes the next delimiter, or len+1
count = count + 1
print '(i0, a, i0, a, i0, 3a)', count, ': positions ', first, ' to ', pos - 1, ' = [', spec(first:pos - 1), ']'
end do
end subroutine show_forward
subroutine show_backward(spec)
character(len=*), intent(in) :: spec
integer :: last, pos
print '(3a)', 'backward: "', spec, '"'
pos = len(spec) + 1 ! len+1 means: after the last character
do
last = pos - 1
call split(spec, delimiters, pos, back=.true.) ! pos becomes the previous delimiter, or 0
print '(3a)', ' [', spec(pos + 1:last), ']'
if (pos == 0) exit
end do
end subroutine show_backward
end program split_loop
forward: "nx=41,,ny=21;alpha=1.0"
1: positions 1 to 5 = [nx=41]
2: positions 7 to 6 = []
3: positions 8 to 12 = [ny=21]
4: positions 14 to 22 = [alpha=1.0]
forward: ",a,"
1: positions 1 to 0 = []
2: positions 2 to 2 = [a]
3: positions 4 to 3 = []
backward: "nx=41,,ny=21;alpha=1.0"
[alpha=1.0]
[ny=21]
[]
[nx=41]
split is available in both compilers. tokenize is not: the GNU Fortran manual has no page for it, and the compiler treated
the call as a call to an external subroutine named tokenize, which fails at link time.
program tokenize_probe
implicit none
character(len=:), allocatable :: tokens(:)
call tokenize('a,b;c', ',;', tokens)
print '(i0)', size(tokens)
end program tokenize_probe
/work/tokenize_probe.f90:4:(.text+0xd4): undefined reference to `tokenize_'
collect2: error: ld returned 1 exit status
Because the call is treated as an implicit external, GNU Fortran also rejects a file that uses the different forms of
tokenize together, with messages about the argument lists of a procedure the programmer never wrote:
Error: More actual than formal arguments in procedure call at (1)
Error: Type mismatch between actual argument at (1) and actual argument at (2) (INTEGER(4)/CHARACTER(*)).
LLVM Flang 23 implements both forms. The program below runs on Flang; the GNU Fortran messages above are the ones it produces for it:
! Fortran 2023 TOKENIZE. Accepted by LLVM Flang 23; GNU Fortran 16.2 compiles the call but
! fails to link it (see the page text for the real diagnostic).
program tokenize_f2023
implicit none
character(len=*), parameter :: spec = 'nx=41,,ny=21;alpha=1.0'
character(len=:), allocatable :: tokens(:), seps(:)
integer, allocatable :: first(:), last(:)
integer :: i
call tokenize(spec, ',;', tokens) ! form 1: tokens, padded to the longest
print '(a, i0, a, i0)', 'tokens: size=', size(tokens), ' len=', len(tokens)
do i = 1, size(tokens)
print '(i0, 3a)', i, ': [', trim(tokens(i)), ']'
end do
call tokenize(spec, ',;', tokens, seps) ! form 1 with SEPARATOR
print '(a, i0, *(1x, a))', 'separators: ', size(seps), seps
call tokenize(spec, ',;', first, last) ! form 2: positions only, no copy
do i = 1, size(first)
print '(i0, a, i0, a, i0, 3a)', i, ': ', first(i), ':', last(i), ' [', spec(first(i):last(i)), ']'
end do
end program tokenize_f2023
tokens: size=4 len=9
1: [nx=41]
2: []
3: [ny=21]
4: [alpha=1.0]
separators: 3 , , ;
1: 1:5 [nx=41]
2: 7:6 []
3: 8:12 [ny=21]
4: 14:22 [alpha=1.0]
The portable replacement for tokenize is a function written with index or scan, shown in the section on arrays of
strings below; it returns an array of a wrapper type so that tokens keep their exact length, which tokenize cannot do. When
only split is needed it is the better primitive: it works with both compilers and does not allocate.
Numbers and strings
Fortran has no to_string or parse_float in the language. The conversion is done by internal I/O: a write whose unit is a
character variable converts values to text, and a read from a character variable converts text to values, with the same
format machinery as for files (J3/23-007r1, 12.4). The variable is the internal file; a scalar variable is one record, an
array is a file of as many records as it has elements, and an internal write fails if the text does
not fit in the record (GNU Fortran reports an end-of-file error).
Two idioms cover nearly every case. To convert a value to text, write it into a buffer that is large enough, with a format
that does not pad (i0, f0.3, g0), and use trim(adjustl(buffer)). To convert text to a value, read from the string with
iostat= and check it; a list-directed read (read (text, *) x) accepts any valid number with surrounding blanks and stops
at a comma or a blank, while an explicit format is stricter. Both appear in this module, which also shows a generic to_string:
module heatflow_convert
use, intrinsic :: iso_fortran_env, only: int32, real64
implicit none
private
public :: to_string, parse_real, parse_int
interface to_string
module procedure int_to_string, real_to_string
end interface to_string
contains
! Conversion to a string: write into a buffer big enough for any value, then trim.
function int_to_string(value) result(text)
integer(int32), intent(in) :: value
character(len=:), allocatable :: text
character(len=16) :: buffer ! 11 characters are enough for an int32
write (buffer, '(i0)') value ! i0: minimum width, no blanks
text = trim(buffer)
end function int_to_string
function real_to_string(value, fmt) result(text)
real(real64), intent(in) :: value
character(len=*), intent(in), optional :: fmt
character(len=:), allocatable :: text
character(len=64) :: buffer
if (present(fmt)) then
write (buffer, fmt) value
else
write (buffer, '(g0)') value ! g0: shortest field that shows the value
end if
text = trim(adjustl(buffer))
end function real_to_string
! Conversion from a string: list-directed internal read, errors reported through iostat.
subroutine parse_real(text, value, stat, msg)
character(len=*), intent(in) :: text
real(real64), intent(out) :: value
integer, intent(out) :: stat
character(len=*), intent(inout) :: msg ! fixed length: portable before Fortran 2023
read (text, *, iostat=stat, iomsg=msg) value
end subroutine parse_real
subroutine parse_int(text, value, stat)
character(len=*), intent(in) :: text
integer(int32), intent(out) :: value
integer, intent(out) :: stat
read (text, '(i10)', iostat=stat) value
end subroutine parse_int
end module heatflow_convert
program number_string
use, intrinsic :: iso_fortran_env, only: int32, real64
use heatflow_convert, only: to_string, parse_real, parse_int
implicit none
character(len=:), allocatable :: label
character(len=128) :: msg
real(real64) :: x
integer(int32) :: n
integer :: stat
label = 'nx=' // to_string(41_int32) // ' alpha=' // to_string(1.0_real64)
print '(2a)', 'label: ', label
print '(2a)', 'g0 of 0.1: ', to_string(0.1_real64)
print '(2a)', 'g0 of 1.0e-7: ', to_string(1.0e-7_real64)
print '(2a)', 'g0 of 123456.5: ', to_string(123456.5_real64)
print '(2a)', 'f8.3 of pi: ', to_string(3.14159265_real64, '(f8.3)')
print '(2a)', 'es12.4 of 1e6: ', to_string(1.0e6_real64, '(es12.4)')
msg = ''
call parse_real(' 0.25 ', x, stat, msg)
print '(a, g0, a, i0)', 'parsed ', x, ' stat=', stat
call parse_real('1.5e-3,7', x, stat, msg) ! list-directed: stops at the comma
print '(a, g0, a, i0)', 'parsed ', x, ' stat=', stat
call parse_real('abc', x, stat, msg)
print '(a, i0, 2a)', 'bad input: stat=', stat, ' msg=', trim(msg)
call parse_real('', x, stat, msg)
print '(a, i0, 2a)', 'empty input: stat=', stat, ' msg=', trim(msg)
call parse_int(' 42', n, stat)
print '(a, i0, a, i0)', 'int ', n, ' stat=', stat
call parse_int('4x2', n, stat)
print '(a, i0)', 'bad int stat=', stat
end program number_string
The output of GNU Fortran 16.2:
label: nx=41 alpha=1.0000000000000000
g0 of 0.1: 0.10000000000000001
g0 of 1.0e-7: 0.99999999999999995E-007
g0 of 123456.5: 123456.50000000000
f8.3 of pi: 3.142
es12.4 of 1e6: 1.0000E+06
parsed 0.25000000000000000 stat=0
parsed 0.15000000000000000E-002 stat=0
bad input: stat=5010 msg=Bad real number in item 1 of list input
empty input: stat=-1 msg=End of file
int 42 stat=0
bad int stat=5010
LLVM Flang 23 printed different text for the lines that depend on processor-defined behaviour; everything else was identical:
label: nx=41 alpha=1.
g0 of 0.1: .1
g0 of 1.0e-7: .1E-6
g0 of 123456.5: 123456.5
f8.3 of pi: 3.142
es12.4 of 1e6: 1.0000E+06
parsed .25 stat=0
parsed .15E-2 stat=0
bad input: stat=1030 msg=Bad real input data at column 2 of record 1
empty input: stat=-1 msg=End of file during input
int 42 stat=0
bad int stat=1001
Three differences between the compilers show up there and deserve attention:
-
g0for reals is processor dependent. The standard says only thatG0follows the rules forGw.dEewith reasonable processor-dependentw,dandeand with blanks removed (13.7.5.2.3). GNU Fortran prints enough digits to round-trip the value (0.10000000000000001), and Flang prints the shortest representation and omits the leading zero (.1, and1.for 1.0). A program that must produce the same text on both compilers uses an explicit format such asf8.3ores12.4. -
f0.3omits the leading zero on both compilers (.500), which is allowed by the standard. Fortran 2023 added thelz,lzsandlzpcontrol edit descriptors and theleading_zero=specifier for this (Introduction of J3/23-007r1). Flang acceptedlzpwith a fixed width (0.500withf8.3) but still printed.500forf0.3, and GNU Fortran 16.2 rejectedlzpwithLegacy Extension: Missing comma in FORMAT string. -
The
iostatvalues and theiomsgtexts are processor-defined. The standard fixes only the sign: positive for an error condition, negative for the end of file or end of record, and zero for success. Teststat /= 0and print the message; do not compare with a number.
A format that builds a file name or a CSV row follows the same pattern. Zero-padding is i6.6, a field without padding is
i0 or f0.d (with the leading-zero caveat above), and the unlimited repeat *(...) writes a variable number of items. The colon
edit descriptor : stops the format at the first item that has no data, which removes the trailing separator:
program build_strings
use, intrinsic :: iso_fortran_env, only: real64
implicit none
character(len=48) :: buffer
character(len=:), allocatable :: filename, row
integer :: step, i
real(real64) :: u(3)
step = 125
write (buffer, '(a, i6.6, a)') 'heatflow_step_', step, '.csv' ! zero-padded step number
filename = trim(buffer)
print '(2a)', 'file name: ', filename
u = [0.5_real64, 12.25_real64, 100.0_real64]
row = ''
do i = 1, size(u)
write (buffer, '(f0.3)') u(i) ! f0: smallest field, sign and digits only
if (i > 1) row = row // ','
row = row // trim(buffer)
end do
print '(2a)', 'row with f0.3: ', row
write (buffer, '(*(g0, :, ","))') step, 'ftcs', .true., 2 ! g0 on integer, character, logical
print '(2a)', 'row with g0: ', trim(buffer)
write (buffer, '(*(f8.3, :, ","))') u
print '(2a)', 'row with f8.3: ', trim(buffer)
write (buffer, '(*(es12.4e3, :, ";"))') u
print '(2a)', 'row with es: ', trim(buffer)
end program build_strings
file name: heatflow_step_000125.csv
row with f0.3: .500,12.250,100.000
row with g0: 125,ftcs,T,2
row with f8.3: 0.500, 12.250, 100.000
row with es: 5.0000E-001; 1.2250E+001; 1.0000E+002
Source: J3/23-007r1 clauses 13.4 (Interaction between input/output list and format), 13.7.2 (Numeric editing) and 13.8.3 (Colon editing) and Fortran Programming Language: Quickstart, Arrays and strings
The g0 descriptor is convenient for building heterogeneous rows because it applies the edit descriptor that suits the
type: an integer prints as i0, a logical as l1 and a character as a without padding (13.7.5.2.2, 13.7.5.3 and 13.7.5.4).
For real values the choice of digits is the processor’s, as seen above.
The AT edit descriptor (Fortran 2023)
The a edit descriptor writes a string with its full length, including trailing blanks, and that is why trim appears in
every print of a fixed-length variable. Fortran 2023 adds the at edit descriptor, which writes the value with trailing
blanks removed and with no field width; on a string of blanks it writes nothing. It is for output only (J3/23-007r1, 13.7.4).
! Fortran 2023 AT edit descriptor (clause 13.7.4). Accepted by LLVM Flang 23.
! GNU Fortran 16.2 rejects it (see the page text for the real diagnostic).
program at_edit
implicit none
character(len=12) :: solver = 'ftcs'
character(len=12) :: blank = ' '
integer :: n = 41
write (*, '("[", a, "]")') solver ! a: the whole field, 12 characters
write (*, '("[", at, "]")') solver ! at: trailing blanks removed
write (*, '("[", at, "]")') blank ! all blanks: no output at all
write (*, '(at, "=", i0)') solver, n ! ftcs=41, no trim() needed
end program at_edit
LLVM Flang 23 accepts it:
[ftcs ]
[ftcs]
[]
ftcs=41
GNU Fortran 16.2 does not recognise at as an edit descriptor. It reads a followed by the position descriptor t and
complains that the comma is missing:
at_edit.f90:10:19:
10 | write (*, '("[", at, "]")') solver ! at: trailing blanks removed
| 1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
at_edit.f90:11:19:
11 | write (*, '("[", at, "]")') blank ! all blanks: no output at all
| 1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
at_edit.f90:12:14:
12 | write (*, '(at, "=", i0)') solver, n ! ftcs=41, no trim() needed
| 1
Error: Legacy Extension: Missing comma in FORMAT string at (1)
The Flang documentation of Fortran 2023 support (read on 2026-10-18) lists the at edit descriptor as not implemented, but the
build checked here accepted and executed it; the table is behind the compiler. Until both compilers agree, write
a with trim (write (*, '(a, "=", i0)') trim(solver), n), which is valid in every standard and costs nothing.
Automatic allocation of iomsg, errmsg and internal-file variables (Fortran 2023)
Before Fortran 2023 the iomsg= variable of an I/O statement and the errmsg= variable of allocate, deallocate and the
image-control statements had to be a character variable that already had a length, and the message was truncated or padded
to it. That forced code to guess a size: character(len=256) :: msg. Fortran
2023 lets the variable be a deferred-length allocatable scalar, in which case the processor allocates it with the length of
the message (Introduction, "Data usage and computation" and "Input/output"). The same release lets a deferred-length
allocatable scalar be the unit of an internal write, in which case it is allocated to the length of the record written
(12.4). Both are behaviour changes: code that relied on a fixed-length msg keeps working, but an allocatable one now has
a defined meaning where it used to be an error.
The three programs below use the new behaviour. Neither compiler implements it. In GNU Fortran 16.2 and in LLVM Flang 23 the variable stays unallocated, and then the compiler either crashes or reports an end of file:
! Fortran 2023: a deferred-length allocatable iomsg variable is allocated by the processor.
! Neither GNU Fortran 16.2 nor LLVM Flang 23 implements this yet (see the page text).
program iomsg_f2023
implicit none
character(len=:), allocatable :: message
integer :: stat, unit
open (newunit=unit, file='no-such-file.nml', status='old', action='read', iostat=stat, iomsg=message)
print '(a, i0)', 'stat=', stat
print '(a, l1)', 'allocated(message)=', allocated(message)
if (allocated(message)) print '(a, i0, 3a)', 'len=', len(message), ' [', message, ']'
end program iomsg_f2023
GNU Fortran 16.2 (with -fcheck=all), then LLVM Flang 23:
iomsg_f2023.f90:8:103:
8 | open (newunit=unit, file='no-such-file.nml', status='old', action='read', iostat=stat, iomsg=message)
| ^
Warning: '.message' may be used uninitialized [-Wmaybe-uninitialized]
iomsg_f2023.f90:3:19:
3 | program iomsg_f2023
| ^
note: '.message' was declared here
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
Segmentation fault
stat=2
allocated(message)=F
Source: J3/23-007r1 (Fortran 2023)
! Fortran 2023: a deferred-length allocatable scalar internal file in WRITE is allocated
! to the length of the record written. Not implemented by GNU Fortran 16.2 or LLVM Flang 23.
program internal_write_f2023
implicit none
character(len=:), allocatable :: line
write (line, '(a, i0, a, f6.2)') 'nx=', 41, ' alpha=', 1.0
print '(a, i0, 3a)', 'len=', len(line), ' [', line, ']'
end program internal_write_f2023
GNU Fortran 16.2, then LLVM Flang 23:
internal_write_f2023.f90:7:60:
7 | write (line, '(a, i0, a, f6.2)') 'nx=', 41, ' alpha=', 1.0
| ^
Warning: '.line' may be used uninitialized [-Wmaybe-uninitialized]
internal_write_f2023.f90:3:28:
3 | program internal_write_f2023
| ^
note: '.line' was declared here
At line 7 of file internal_write_f2023.f90
Fortran runtime error: End of file
Error termination. Backtrace:
fatal Fortran runtime error(/work/internal_write_f2023.f90:7): Internal write overran available records
Aborted
The same happens for errmsg= in a deallocate statement:
! Fortran 2023: a deferred-length allocatable errmsg variable is allocated by the processor.
! Neither GNU Fortran 16.2 nor LLVM Flang 23 does it yet (see the page text).
program errmsg_f2023
implicit none
character(len=:), allocatable :: message
real, allocatable :: u(:)
integer :: stat
deallocate (u, stat=stat, errmsg=message) ! u is not allocated: an error condition
print '(a, l1)', 'stat /= 0: ', stat /= 0
print '(a, l1)', 'allocated(message)=', allocated(message)
if (allocated(message)) print '(a, i0, 3a)', 'len=', len(message), ' [', message, ']'
end program errmsg_f2023
GNU Fortran 16.2, then LLVM Flang 23:
errmsg_f2023.f90:9:92:
9 | deallocate (u, stat=stat, errmsg=message) ! u is not allocated: an error condition
| ^
Warning: '.message' may be used uninitialized [-Wmaybe-uninitialized]
errmsg_f2023.f90:3:20:
3 | program errmsg_f2023
| ^
note: '.message' was declared here
Program received signal SIGSEGV: Segmentation fault - invalid memory reference.
Backtrace for this error:
Segmentation fault
stat /= 0: T
allocated(message)=F
The portable form is a fixed-length buffer that is large enough, trimmed after use, which is what the parse_real routine in
the previous section does with its msg argument. For an internal write, use the buffer-and-trim idiom shown there; the
heatflow_config module of the running example reads its namelist with a character(len=*) message argument for the same
reason. A message longer than the buffer is truncated without an error.
Character kinds, Unicode and UTF-8
Each processor provides a default character kind and may provide others. selected_char_kind(name) returns the kind number for
'default', 'ascii' or 'iso_10646' (case and trailing blanks are ignored) and -1 if the processor lacks it, and a
character literal of a given kind is written with the kind name and an underscore before the delimiter. The ASCII kind is the
character set of ISO/IEC 646, and the ISO 10646 kind is UCS-4, the full Unicode repertoire with one character per code point
(J3/23-007r1, 7.4.4.1 and 16.9.180). Both compilers used here provide kind 1 for default and ASCII, and kind 4 for ISO 10646;
the kind numbers are not portable (the standard does not fix them), so use the values
returned by selected_char_kind rather than numbers.
Three layers have to be kept apart:
-
The kind of the variable decides the character set in memory. With the ISO 10646 kind,
lencounts code points, andiacharandacharwork on ASCII codes only. The kind-4 characters have their ownchar(i, kind)andichar. -
The encoding of a file is a property of the connection:
open (unit=u, ..., encoding='utf-8')makes a formatted file Unicode. Characters of the ISO 10646 kind are then converted to and from UTF-8 on the way out and in (12.5.6.9), andinquire (unit=u, encoding=e)returns'UTF-8'for such a connection. Only a formatted connection can have this option; an unformatted file is a sequence of bytes. -
The bytes of the source file. A literal such as
'café'in a default-kind variable holds the bytes of the source file’s encoding: with UTF-8 source the accented character occupies two bytes, solenis 5.
The program writes a string of the ISO 10646 kind to a UTF-8 file, reads the raw bytes back through a stream connection and reads the text again through a Unicode connection:
program char_kinds
use, intrinsic :: iso_fortran_env, only: int8
implicit none
integer, parameter :: ascii = selected_char_kind('ascii')
integer, parameter :: ucs4 = selected_char_kind('iso_10646')
character(kind=ucs4, len=:), allocatable :: label
character(kind=ucs4, len=20) :: readback
character(len=:), allocatable :: plain
integer(int8), allocatable :: bytes(:)
integer :: unit, stat, size_in_bytes, i
print '(a, i0)', "selected_char_kind('default') = ", selected_char_kind('default')
print '(a, i0)', "selected_char_kind('ascii') = ", ascii
print '(a, i0)', "selected_char_kind('iso_10646') = ", ucs4
print '(a, i0)', "selected_char_kind('klingon') = ", selected_char_kind('klingon')
print '(a, i0)', "kind('a') = ", kind('a')
! The degree sign and the Greek letter alpha come from their Unicode code points.
label = ucs4_'Temperature ' // char(int(z'00B0'), ucs4) // ucs4_'C, ' // char(int(z'03B1'), ucs4)
print '(a, i0)', 'len(label) in characters = ', len(label)
open (newunit=unit, file='label.txt', status='replace', action='write', encoding='utf-8')
write (unit, '(a)') label
close (unit)
open (newunit=unit, file='label.txt', status='old', action='read', access='stream', form='unformatted')
inquire (unit, size=size_in_bytes)
allocate (bytes(size_in_bytes))
read (unit) bytes
close (unit)
print '(a, i0)', 'file size in bytes = ', size_in_bytes
print '(a, *(z2.2, 1x))', 'bytes = ', (iand(int(bytes(i)), 255), i = 13, size_in_bytes)
open (newunit=unit, file='label.txt', status='old', action='read', encoding='utf-8')
read (unit, '(a)', iostat=stat) readback
close (unit)
print '(a, i0, a, i0)', 'stat=', stat, ' len_trim(readback)=', len_trim(readback)
print '(a, l1)', 'round trip equals label: ', trim(readback) == label
plain = 'caf' // char(int(z'C3')) // char(int(z'A9')) ! the two UTF-8 bytes of an accented e
print '(a, i0)', 'default-kind string holding UTF-8: len = ', len(plain)
end program char_kinds
selected_char_kind('default') = 1
selected_char_kind('ascii') = 1
selected_char_kind('iso_10646') = 4
selected_char_kind('klingon') = -1
kind('a') = 1
len(label) in characters = 17
file size in bytes = 20
bytes = C2 B0 43 2C 20 CE B1 0A
stat=0 len_trim(readback)=17
round trip equals label: T
default-kind string holding UTF-8: len = 5
Source: J3/23-007r1 (Fortran 2023)
The file has 20 bytes for 17 characters because the degree sign (C2 B0) and the Greek letter (CE B1) take two bytes each, and
the final byte is the newline that ends the record. A default-kind literal that contains non-ASCII text behaves as bytes on both
compilers:
program utf8_literal
implicit none
character(len=:), allocatable :: s
s = 'café'
print '(a, i0, 2a)', 'len=', len(s), ' ', s
end program utf8_literal
len=5 café
Mixing kinds follows strict rules. A formatted transfer to an internal file of default kind cannot carry a character item of a nondefault kind, an internal file of the ISO 10646 kind accepts items of ISO 10646 and ASCII kinds, and comparison and concatenation need operands of the same kind (12.6.3). Assignment between kinds converts, and it is a conversion that can lose information. Programs that handle Unicode text beyond display purposes, normalisation or grapheme clusters need a library; the language knows only code points.
C strings (Fortran 2023)
A C string is an array of char ended by a null character, while a Fortran string carries its length and may contain null
characters. The iso_c_binding module has c_char and c_null_char for building such strings by hand, as in
trim(name) // c_null_char. Fortran 2023 adds two procedures to remove that boilerplate (J3/23-007r1, 18.2.3.9 and 18.2.3.5):
f_c_string(string [, asis]) returns the string followed by a null character, with trailing blanks removed unless asis is
true, and c_f_strpointer makes a deferred-length character pointer from a C address or an array of c_char, with length up
to the first null character. f_c_string is accepted by both compilers:
! Fortran 2023 F_C_STRING (clause 18.2.3.9): accepted by GNU Fortran 16.2 and LLVM Flang 23.
program f_c_string_demo
use, intrinsic :: iso_c_binding, only: c_char, f_c_string
implicit none
character(kind=c_char, len=16) :: padded = 'heatflow.csv'
character(kind=c_char, len=:), allocatable :: c_text, c_text_asis
c_text = f_c_string(padded) ! trailing blanks removed, then a null appended
c_text_asis = f_c_string(padded, asis=.true.) ! every character kept, then a null appended
print '(a, i0, a, i0)', 'default: len=', len(c_text), ' last code=', iachar(c_text(len(c_text):len(c_text)))
print '(a, i0)', 'asis=.true.: len=', len(c_text_asis)
end program f_c_string_demo
default: len=13 last code=0
asis=.true.: len=17
c_f_strpointer is accepted by LLVM Flang 23 but not by GNU Fortran 16.2:
! Fortran 2023 C_F_STRPOINTER (clause 18.2.3.5): accepted by LLVM Flang 23, not by GNU Fortran 16.2.
program c_f_strpointer_demo
use, intrinsic :: iso_c_binding, only: c_char, c_null_char, c_ptr, c_loc, c_f_strpointer
implicit none
character(kind=c_char, len=:), allocatable, target :: c_text
character(kind=c_char, len=:), pointer :: view
type(c_ptr) :: address
c_text = 'heatflow.csv' // c_null_char
address = c_loc(c_text(1:1))
call c_f_strpointer(address, view, len(c_text))
print '(a, i0)', 'len(c_text) = ', len(c_text)
print '(a, i0)', 'len(view) = ', len(view)
end program c_f_strpointer_demo
LLVM Flang 23 printed:
len(c_text) = 13
len(view) = 13
GNU Fortran 16.2 stopped at the use statement:
c_f_strpointer.f90:3:75:
3 | use, intrinsic :: iso_c_binding, only: c_char, c_null_char, c_ptr, c_loc, c_f_strpointer
| 1
Error: Symbol 'c_f_strpointer' referenced at (1) not found in intrinsic module ISO_C_BINDING
The Flang result differs from the standard text, whose rule is that the length of the pointer becomes the largest value for
which no null character appears in the sequence, which for this string is 12: the pointer length printed by Flang includes the null
character. Treat the length from this procedure as unverified until the compilers agree, and use strlen through an
interface to the C library when it matters. The Flang documentation also lists these two procedures as not implemented; the
build checked here accepts them. The C side, including descriptors for character arguments, is covered on
Interoperability with C and
C Descriptors and ISO_Fortran_binding.
Arrays of strings
A character(len=10) :: names(3) is an array of three strings, each exactly 10 characters, and every element has the same
length. That is the right type for a list of fixed-size items, such as the solver names of the heatflow configuration. All the
array machinery applies: any(names == 'jacobi'), findloc(names, 'jacobi', 1), maxval(len_trim(names)), array
constructors and sections, and a substring of an element is written names(2)(1:3).
Two rules concern array constructors. In Fortran 90 every element of a constructor of strings must have the same length, so
['ftcs', 'jacobi'] is an error; since Fortran 2003 a type specification fixes the length and shorter elements are padded:
[character(len=10) :: 'ftcs', 'jacobi'].
! Deliberately non-conforming: the elements of the array constructor have different lengths.
program ctor_lengths
implicit none
character(len=10) :: solvers(3)
solvers = ['ftcs', 'jacobi', 'multigrid']
print '(a)', solvers
end program ctor_lengths
GNU Fortran 16.2 rejects it:
ctor_lengths.f90:5:20:
5 | solvers = ['ftcs', 'jacobi', 'multigrid']
| 1
Error: Different CHARACTER lengths (4/6) in array constructor at (1)
LLVM Flang 23 accepts it as a documented extension (the longest element determines the length) and prints the three names padded to 10 characters. Add the type specification so the file is also valid for GNU Fortran and every conforming compiler.
When the strings have different lengths and the lengths matter (tokens, lines of a file, key and value), an array of
character(len=:) is not possible, because all elements of an array share one type parameter. The standard answer is a derived
type with a deferred-length allocatable component, and an array of that type. The module below defines it together with
split_string, a portable replacement for tokenize that returns exact-length tokens, and with join, the inverse:
module heatflow_strings
implicit none
private
public :: string_t, split_string, join
! Elements of a character array share one length; this wrapper gives each element its own.
type :: string_t
character(len=:), allocatable :: value
end type string_t
contains
! Portable stand-in for TOKENIZE: works with every Fortran 2003 compiler.
function split_string(text, delimiters) result(parts)
character(len=*), intent(in) :: text, delimiters
type(string_t), allocatable :: parts(:)
integer :: n, i, start, k
n = 1
do i = 1, len(text)
if (index(delimiters, text(i:i)) > 0) n = n + 1
end do
allocate (parts(n))
start = 1
do k = 1, n
i = scan(text(start:), delimiters) ! 0 when no delimiter is left
if (i == 0) then
parts(k)%value = text(start:)
else
parts(k)%value = text(start:start + i - 2) ! may be zero-length
start = start + i
end if
end do
end function split_string
function join(parts, separator) result(text)
type(string_t), intent(in) :: parts(:)
character(len=*), intent(in) :: separator
character(len=:), allocatable :: text
integer :: i
text = ''
do i = 1, size(parts)
if (i > 1) text = text // separator
text = text // parts(i)%value
end do
end function join
end module heatflow_strings
program string_arrays
use heatflow_strings, only: string_t, split_string, join
implicit none
character(len=10) :: solvers(3) ! fixed length: every element has 10 characters
character(len=:), allocatable :: line
type(string_t), allocatable :: parts(:), pair(:)
integer :: i
solvers = ['ftcs ', 'jacobi ', 'multigrid ']
print '(a, i0, a, i0)', 'len(solvers)=', len(solvers), ' size=', size(solvers)
print '(a, i0)', 'longest name: ', maxval(len_trim(solvers))
print '(a, l1)', "any(solvers == 'jacobi'): ", any(solvers == 'jacobi')
print '(a, i0)', "findloc(solvers, 'jacobi', 1): ", findloc(solvers, 'jacobi', 1)
print '(3a)', 'element 2 is [', solvers(2), ']'
print '(3a)', 'its substring (1:3) is [', solvers(2)(1:3), ']' ! substring of an array element
! A constructor with differing lengths needs a type-spec, or the lengths must match.
solvers = [character(len=10) :: 'ftcs', 'jacobi', 'multigrid']
print '(*(a, :, "|"))', (trim(solvers(i)), i = 1, size(solvers))
line = 'nx=41,ny=21,,solver=ftcs'
parts = split_string(line, ',')
print '(a, i0, a)', 'split into ', size(parts), ' pieces:'
do i = 1, size(parts)
print '(i0, a, i0, 3a)', i, ': len=', len(parts(i)%value), ' [', parts(i)%value, ']'
end do
pair = split_string(parts(4)%value, '=') ! key and value of the last piece
print '(5a)', 'key=', pair(1)%value, ' value=', pair(2)%value
print '(2a)', 'joined: ', join(parts, ';')
print '(a, i0)', 'characters in all pieces: ', sum([(len(parts(i)%value), i = 1, size(parts))])
end program string_arrays
len(solvers)=10 size=3
longest name: 9
any(solvers == 'jacobi'): T
findloc(solvers, 'jacobi', 1): 2
element 2 is [jacobi ]
its substring (1:3) is [jac]
ftcs|jacobi|multigrid
split into 4 pieces:
1: len=5 [nx=41]
2: len=5 [ny=21]
3: len=0 []
4: len=11 [solver=ftcs]
key=solver value=ftcs
joined: nx=41;ny=21;;solver=ftcs
characters in all pieces: 21
Source: J3/23-007r1 clauses 7.5.4 (Components), 7.8 (Construction of array values) and 16.9.179 (SCAN) and Fortran Programming Language: Quickstart, Arrays and strings
The derived type costs one allocation per element and cannot be printed directly (a derived type with an allocatable component needs a defined output procedure, see Derived-Type and Asynchronous I/O), but it is the only form in which strings of different lengths can live in one array. Derived types themselves are covered on Derived Types.
The Fortran standard library
The Fortran standard library (stdlib) supplies what the language lacks for strings, in the modules stdlib_ascii (character
classification and case conversion), stdlib_strings (strip, chomp, starts and ends tests, slicing, search, replacement,
padding, zero-fill, join, to_string for numbers and to_c_char), stdlib_string_type (a string_type derived type that
hides its representation and overloads len, trim, adjustl, comparison, concatenation and formatted I/O) and
stdlib_stringlist_type (a list of strings). The specification pages mark these procedures experimental on the date of the
check (2026-10-18), so their interfaces can still change; they are listed in more detail on
Fortran stdlib.
The program below was built with fpm against the stdlib-fpm branch of stdlib, which is a generated fpm-ready snapshot of the
library (commit 5465df3 on 2026-10-04, library version 0.8.1), under fpm’s default compiler flags rather than the strict flag
set used for the other examples, since stdlib itself is not built under them. It is a checked example, not part of the
standalone set:
program main
use stdlib_string_type, only: string_type, char, len, len_trim, to_upper, assignment(=), operator(//), operator(==)
use stdlib_strings, only: strip, starts_with, ends_with, replace_all, join, to_string, find, count, slice, padl, zfill
implicit none
type(string_type) :: s, t, parts(3)
integer :: i
s = ' ftcs solver '
t = strip(s)
print '(3a)', '[', char(t), ']'
print '(a, i0, a, i0)', 'len=', len(s), ' len_trim=', len_trim(s)
print '(2a)', 'upper: ', char(to_upper(t))
print '(2a)', 'concat: ', char(t // ' v2')
print '(a, l1)', 'equal: ', t == 'ftcs solver'
print '(a, l1, l1)', 'starts/ends: ', starts_with(char(t), 'ftcs'), ends_with(char(t), 'solver')
print '(2a)', 'replace_all: ', replace_all(char(t), 'ftcs', 'jacobi')
print '(a, i0)', 'find: ', find(char(t), 'solver')
print '(a, i0)', 'count: ', count('a,b,c,d', ',')
print '(2a)', 'slice: ', slice(char(t), 1, 4)
print '(2a)', 'to_string: ', to_string(41)
print '(2a)', 'padl: ', padl('41', 6, '0')
print '(2a)', 'zfill: ', zfill('41', 6)
parts(1) = 'nx'; parts(2) = 'ny'; parts(3) = 'alpha'
print '(2a)', 'join: ', char(join(parts, ','))
end program
Project is up to date
[ftcs solver]
len=15 len_trim=13
upper: FTCS SOLVER
concat: ftcs solver v2
equal: T
starts/ends: TT
replace_all: jacobi solver
find: 6
count: 3
slice: ftcs
to_string: 41
padl: 000041
zfill: 000041
join: nx,ny,alpha
A string_type cannot be given directly to a print list unless the module’s formatted-write procedure is imported
(write(formatted) in the only list), which is why the example converts with char before printing. Whether to use it is a
design decision: string_type makes the code read like a string class and removes the trimming and reallocation details
shown in this page, at the price of a dependency, a type that hides its length and a conversion at every interface with the
rest of Fortran, where character(len=*) is the common currency.
Where the books are outdated
The treatment of strings in the book Modern Fortran Explained (sections 2.12 and 3.7) and of the Fortran 2023 additions (sections 23.2.2 and 23.3.1) is organised by language feature and gives no information about which compilers implement them, which is what a programmer needs today. The points below are the ones where a reader following the book alone would go wrong on current toolchains.
| Book content | Current state |
|---|---|
|
|
The |
Not implemented by GNU Fortran 16.2. Flang 23 accepts |
|
|
Source form limits for long string literals |
The 132-character line and the 255 continuation lines of the Fortran 2018 limits are replaced in Fortran 2023 by 10,000 characters per line, no limit on continuation lines and 1,000,000 characters per statement; see Program Structure and Source Form. |
|
|
Assumed-length character functions |
Obsolescent (Annex B.3.6); use an automatic length or a deferred-length result. |
|
They are not obsolescent in the standard. |
Standard version provenance and compiler support
| Feature | Introduced | GNU Fortran 16.2 | LLVM Flang 23 (checked 2026-10-18) |
|---|---|---|---|
|
Fortran 77 and earlier |
yes |
yes |
|
Fortran 90 |
yes |
yes |
|
Fortran 2003 |
yes |
yes (constructor with different lengths accepted as an extension) |
|
Fortran 2008 |
yes |
yes |
|
Fortran 2023 |
yes |
yes |
|
Fortran 2023 |
no: link error |
yes, both forms and |
|
Fortran 2023 |
no: |
yes (documentation table says not yet) |
Deferred-length allocatable |
Fortran 2023 |
no: segmentation fault or end-of-file error |
no: variable stays unallocated or |
|
Fortran 2023 |
yes |
yes (documentation table says not yet) |
|
Fortran 2023 |
no: not found in |
yes, but the pointer length differs from the standard text |
|
Fortran 2023 |
no: |
accepted; no effect on |
Not checked here, because no compiler for them is installed: Intel ifx, NAG, HPE Cray and NVIDIA HPC compilers. See History, Standards and Compilers for the per-compiler support table, and What’s New and Fortran 2028 Preview for the features added by Fortran 2023 and the ones planned for Fortran 2028.
Pitfalls
-
Trailing blanks are invisible and never compared.
'ftcs'equals'ftcs ', so uselenandlen_trim, not==, to tell them apart, andtrimbefore concatenating, or the padding becomes part of the result. -
s = 'x' // tands(:) = 'x' // tare different statements. The first reallocatess, the second pads or truncates to the existing length, and on an unallocatedsthe second is an error. -
A fixed-length buffer silently truncates.
character(len=16) :: namefollowed by an assignment of 20 characters keeps 16 and reports nothing. Checklen_trim(src) <= len(dst)where data matters. -
A substring is not range-checked by default.
s(i:j)outside the string reads other memory. Compile test builds with-fcheck=all. -
len(unallocated)is undefined. Testallocated(s)before using a deferred-length variable that a branch may not have set, and remember that an allocated zero-length string is also legal. -
indexandscanare not the same.indexlooks for a substring,scanfor any character of a set, andverifyfor any character that is not in a set. Passing a one-character set to the wrong one gives wrong positions but valid code. -
Case conversion does not exist as an intrinsic, and
achar(iachar(c) - 32)changes any character, not only letters. Test the range first, and do not use it for non-ASCII text. -
g0of a real is not portable. The digits and the leading zero differ between compilers; use a fixed format when the text is compared or parsed by another program. -
f0.dmay omit the leading zero (.500); a CSV consumer that rejects it needsf8.3plusadjustl, or the Fortran 2023lzpwhere both compilers accept it. -
Internal
readstops at a blank or comma with*.read (text, *) xaccepts'0.25'and'0.25 trailing'; use an explicit format and checkiostatwhen the whole field must be a number. -
iostatvalues differ between compilers.5010from GNU Fortran and1030from Flang were both a bad number; compare with zero only. -
The Fortran 2023 conveniences are not universal.
tokenize,at, automatic allocation ofiomsgandc_f_strpointerfail on one or both compilers checked here; keep thetrimand buffer idioms for code that must build everywhere. -
A default-kind string with UTF-8 text counts bytes.
len('café')is 5,indexand substrings cut in the middle of a character, and upper-casing by ASCII code corrupts the text. Use the ISO 10646 kind when characters matter. -
An array of strings has one length.
character(len=:), allocatable :: a(:)cannot hold tokens of different lengths; use a wrapper derived type. -
Do not pass a shorter actual argument to a dummy of explicit length. Use
character(len=*)for dummies that only read. -
character*n, fixed form and Hollerith constants belong to old code; see Legacy Features and Modernization.
See also Best Practices and Style, Common Gotchas and Pitfalls, Expressions and Assignment for the assignment and operator rules, Intrinsic Procedures Overview for the other intrinsic classes and Input/Output: Units and Files for the connection options mentioned above.
References
-
Fortran Programming Language: Quickstart, Arrays and strings
-
GNU Fortran manual: Options for code generation conventions (-fcheck and -frealloc-lhs)
-
GNU Fortran manual: Options to request or suppress errors and warnings (-Wcharacter-truncation)
-
Flang documentation: Fortran standards support and Extensions
-
Fortran stdlib: stdlib_strings, stdlib_string_type, stdlib_stringlist_type and stdlib_ascii