Yes, but...
Functions can modify their arguments.
FUNCTION f1(in, status)
INTEGER, INTENT(IN) :: in
INTEGER, INTENT(OUT) :: status
INTEGER :: f1
...
! Function result must be defined even if the
! function "fails".
f1 = ...
status = ...
END FUNCTION f1
...
INTEGER :: x, status
x = f1(1, status)
IF (status /= 0) ERROR STOP 'All gone bad!'
There are downsides to this approach (e.g. the function cannot be PURE,
language prohibitions on changes to entities that appear elsewhere
within the statement can be unintentionally violated) such that this is
discouraged by some Fortran style guidelines, including the style
guideline that I follow personally.
Functions can return derived types.
TYPE result_type
INTEGER :: value
INTEGER :: status
END TYPE result_type
FUNCTION f2(in)
INTEGER, INTENT(IN) :: in
TYPE(result_type) :: f2
...
! Because it is a function result, the object
! of derived type must be completely defined.
f2%value = ...
f2%status = ...
END FUNCTION f2
...
TYPE(result_type) :: y
y = f2(1)
IF (y%status /= 0) ERROR STOP 'I broke it :('
From a style perspective, it may be better to consider whether your
function should be a subroutine.
SUBROUTINE s3(in, value, status)
INTEGER, INTENT(IN) :: in
INTEGER, INTENT(OUT) :: value
INTEGER, INTENT(OUT) :: status
...
! If the subroutine fails, it may leave `value` undefined.
status = ...
END SUBROUTINE s3
...
INTEGER :: z, status
CALL s3(1, z, status)
IF (status /= 0) ERROR STOP 'Oh no, not again.'